সি ++ এ মুভ কনস্ট্রাক্টরগুলির অনুপ্রেরণা এবং ব্যবহার


17

আমি সম্প্রতি সি ++ এ মুভ কনস্ট্রাক্টর সম্পর্কে পড়ছি (উদাহরণস্বরূপ এখানে দেখুন ) এবং আমি বুঝতে চেষ্টা করছি যে তারা কীভাবে কাজ করে এবং কখন তাদের ব্যবহার করা উচিত।

আমি যতদূর বুঝতে পেরেছি, একটি মুভ কনস্ট্রাক্টর বৃহত অবজেক্টগুলি অনুলিপি করার কারণে সৃষ্ট পারফরম্যান্স সমস্যাগুলি দূর করতে ব্যবহৃত হয়। উইকিপিডিয়া পৃষ্ঠাতে বলা হয়েছে: "সি ++ 03 এর সাথে দীর্ঘস্থায়ী পারফরম্যান্সের সমস্যা হ'ল ব্যয়বহুল এবং অপ্রয়োজনীয় গভীর কপিগুলি যা অবজেক্টগুলি যখন মান দ্বারা পাস করা হয় তখন স্পষ্টতই ঘটতে পারে" "

আমি সাধারণত এই ধরনের পরিস্থিতিগুলিকে সম্বোধন করি

  • রেফারেন্স দ্বারা অবজেক্টগুলি পাস করার মাধ্যমে, বা
  • বস্তুর কাছাকাছি যেতে স্মার্ট পয়েন্টার (উদাহরণস্বরূপ বুস্ট :: শেয়ার্ড পিটার) ব্যবহার করে (বস্তুর পরিবর্তে স্মার্ট পয়েন্টারগুলি অনুলিপি করা হয়)।

কোন পরিস্থিতি যেখানে উপরোক্ত দুটি কৌশলগুলি পর্যাপ্ত নয় এবং একটি মুভ কনস্ট্রাক্টর ব্যবহার করা আরও সুবিধাজনক?


1
পদক্ষেপ পদক্ষেপগুলি আরও অনেক কিছু অর্জন করতে পারে (উত্তরে যেমন বলা হয়েছে) ছাড়াও, আপনার জিজ্ঞাসা করা উচিত নয় যে পরিস্থিতিগুলি যেখানে রেফারেন্সের মাধ্যমে বা স্মার্ট পয়েন্টারের মাধ্যমে পাস করা যথেষ্ট নয়, তবে যদি সেই কৌশলগুলি সর্বোত্তম এবং পরিষ্কার উপায় হয় তবে shared_ptrএটি করার জন্য (দ্রুত অনুলিপি করার জন্য godশ্বর একটি সাবধানতা অবলম্বন করুন) এবং যদি পদার্থবিজ্ঞানগুলি প্রায় কোনও কোডিং-, শব্দার্থবিজ্ঞান- এবং পরিষ্কার-পরিচ্ছন্নতা-জরিমানা ছাড়াই এটি অর্জন করতে পারে।
ক্রিস বলেছেন মোনিকা

উত্তর:


16

সরানো শব্দার্থবিজ্ঞানগুলি সি ++ এর সাথে একটি সম্পূর্ণ মাত্রা প্রবর্তন করে - আপনাকে সস্তা করে মানগুলি ফেরত দেওয়ার জন্য কেবল এটি নেই।

উদাহরণস্বরূপ, সরানো-শব্দার্থবিজ্ঞান ছাড়া std::unique_ptrকাজ করে না - দেখুন std::auto_ptr, যা সরানো-শব্দার্থবিজ্ঞানের প্রবর্তনের সাথে অবচিত হয়েছিল এবং সি ++ 17 এ সরানো হয়েছিল। কোনও সংস্থান সরানো এটিকে অনুলিপি করা থেকে সম্পূর্ণ আলাদা। এটি একটি অনন্য আইটেমের মালিকানা স্থানান্তর করার অনুমতি দেয়

উদাহরণস্বরূপ, আসুন না তাকান std::unique_ptr, কারণ এটি মোটামুটি ভালভাবে আলোচিত। ওপেনজিএলে একটি ভার্টেক্স বাফার অবজেক্টটি দেখুন, বলুন ject একটি ভার্টেক্স বাফার জিপিইউতে মেমরির প্রতিনিধিত্ব করে - এটি বিশেষ ক্রিয়াকলাপ ব্যবহার করে বরাদ্দকরণ এবং ডিলোক্যাট করা প্রয়োজন, সম্ভবত এটি কত দিন বেঁচে থাকতে পারে তার জন্য কঠোর বাধা রয়েছে। এটি কেবল গুরুত্বপূর্ণ যে এটি কেবলমাত্র একজন মালিকই ব্যবহার করুন।

class vertex_buffer_object
{
    vertex_buffer_object(size_t size)
    {
        this->vbo_handle = create_buffer(..., size);
    }

    ~vertex_buffer_object()
    {
        release_buffer(vbo_handle);
    }
};

void create_and_use()
{
    vertex_buffer_object vbo = vertex_buffer_object(SIZE);

    do_init(vbo); //send reference, do not transfer ownership

    renderer.add(std::move(vbo)); //transfer ownership to renderer
}

এখন, এটি একটি দিয়ে করা যেতে পারে std::shared_ptr- তবে এই সংস্থানটি ভাগ করে নেওয়া হবে না। এটি ভাগ করে নেওয়া পয়েন্টারটি ব্যবহার করতে বিভ্রান্তিকর করে তোলে। আপনি ব্যবহার করতে পারেন std::unique_ptr, তবে এর জন্য এখনও সরানো শব্দার্থবিজ্ঞানের প্রয়োজন।

স্পষ্টতই, আমি কোনও মুভ কনস্ট্রাক্টর কার্যকর করি নি, তবে আপনি ধারণাটি পেয়ে যান।

এখানে প্রাসঙ্গিক বিষয় হ'ল কিছু সংস্থান অনুলিপিযোগ্য নয় । আপনি সরানোর পরিবর্তে পয়েন্টারগুলির আশেপাশে যেতে পারেন, তবে আপনি যদি ইউনিক_পিটার ব্যবহার না করেন তবে মালিকানার সমস্যা রয়েছে। কোডের উদ্দেশ্য কী তা যতটা সম্ভব স্পষ্ট হওয়া সার্থক, সুতরাং একটি মুভ-কনস্ট্রাক্টর সম্ভবত সেরা পন্থা।


উত্তরের জন্য ধন্যবাদ. যদি কেউ এখানে ভাগ করে নেওয়া পয়েন্টার ব্যবহার করে তবে কী হবে?
জর্জিও

আমি নিজেই উত্তর দেওয়ার চেষ্টা করি: একটি ভাগ করা পয়েন্টার ব্যবহার করা অবজেক্টের আজীবন নিয়ন্ত্রণ করতে দেয় না, যদিও এটি প্রয়োজন যে অবজেক্টটি কেবলমাত্র নির্দিষ্ট সময়ের জন্য বেঁচে থাকতে পারে।
জর্জিও

3
@ জর্জিও আপনি একটি ভাগ করা পয়েন্টার ব্যবহার করতে পারেন, তবে এটি শব্দার্থগতভাবে ভুল হবে। কোনও বাফার ভাগ করা সম্ভব নয়। এছাড়াও, এটি আপনাকে মূলত একটি পয়েন্টারকে পয়েন্টারটি পাস করতে বাধ্য করবে (যেহেতু ভিবিও মূলত জিপিইউ মেমরির জন্য একটি অনন্য পয়েন্টার)। আপনার কোডটি পরে দেখছে এমন কেউ ভাবতে পারে 'কেন এখানে ভাগ করে নেওয়া পয়েন্টার আছে? এটি কি ভাগ করা সম্পদ? এটি একটি বাগ হতে পারে! '। আসল অভিপ্রায়টি কী ছিল তা যতটা সম্ভব পরিষ্কার হওয়া ভাল।
সর্বোচ্চ

@ জর্জিও হ্যাঁ, এটিও প্রয়োজনের একটি অংশ। যখন এই ক্ষেত্রে 'রেন্ডারার' কিছু সংস্থান হ্রাস করতে চায় (জিপিইউতে নতুন সামগ্রীর জন্য সম্ভবত পর্যাপ্ত মেমরি নয়), মেমোরিতে অন্য কোনও হ্যান্ডেল থাকতে হবে না। সুযোগের বাইরে চলে যাওয়া এমন একটি শেয়ার্ড_পিটার ব্যবহার করা যদি আপনি অন্য কোথাও এটি না রাখেন তবে কাজ করবে তবে আপনি যখন পারবেন তখন কেন এটি সম্পূর্ণরূপে প্রকাশ করবেন না?
সর্বোচ্চ

@ জর্জিও স্পষ্ট করার জন্য আরেকবার চেষ্টা করার জন্য আমার সম্পাদনা দেখুন।
সর্বোচ্চ

5

আপনি যখন কোনও মূল্য ফিরিয়ে দিচ্ছেন তখন মুভ শব্দার্থবিজ্ঞানগুলি অগত্যা এত বড় কোনও উন্নতি নয় - এবং / আপনি যদি কোনও shared_ptr(বা অনুরূপ কিছু) ব্যবহার করেন তবে আপনি সম্ভবত অকাল হতাশা করছেন। বাস্তবে, প্রায় সমস্ত যুক্তিসঙ্গতভাবে আধুনিক সংকলকরা রিটার্ন মান অপ্টিমাইজেশন (আরভিও) এবং নামযুক্ত রিটার্ন মান অপ্টিমাইজেশন (এনআরভিও) বলে থাকে called এটির মানে যখন আপনি একটি মান ফিরে করছি, পরিবর্তে আসলে মান অনুলিপি যে সব সময়ে, তারা সহজেই কোনও প্রত্যাশিত পয়েন্টার / রেফারেন্সটি পাস করে যেখানে মানটি ফেরতের পরে নির্ধারিত হতে চলেছে এবং ফাংশনটি ব্যবহার করে যেখানে এটি শেষ হতে চলেছে সেখানে মান তৈরি করতে। সি ++ স্ট্যান্ডার্ড এটিকে অনুমতি দেওয়ার জন্য বিশেষ বিধানগুলি অন্তর্ভুক্ত করে, সুতরাং (যদি উদাহরণস্বরূপ) আপনার অনুলিপি নির্মাণকারীরও পার্শ্ব প্রতিক্রিয়া দেখা দেয় তবে মানটি ফেরত দেওয়ার জন্য অনুলিপি নির্মাণকারীর ব্যবহারের প্রয়োজন হয় না। উদাহরণ স্বরূপ:

#include <vector>
#include <numeric>
#include <iostream>
#include <stdlib.h>
#include <algorithm>
#include <iterator>

class X {
    std::vector<int> a;
public:
    X() {
        std::generate_n(std::back_inserter(a), 32767, ::rand);
    }

    X(X const &x) {
        a = x.a;
        std::cout << "Copy ctor invoked\n";
    }

    int sum() { return std::accumulate(a.begin(), a.end(), 0); }
};

X func() {
    return X();
}

int main() {
    X x = func();

    std::cout << "sum = " << x.sum();
    return 0;
};

এখানে মূল ধারণাটি মোটামুটি সহজ: পর্যাপ্ত সামগ্রী সহ একটি শ্রেণি তৈরি করুন আমরা এটির অনুলিপি করা এড়াতে চাই, যদি সম্ভব হয় (তবে std::vectorআমরা 32767 এলোমেলো ইনটগুলি পূরণ করি)। আমাদের কাছে একটি স্পষ্ট অনুলিপি কর্টর রয়েছে যা কখন / যদি অনুলিপি হয় তা আমাদের দেখায়। আমাদের কাছে অবজেক্টের এলোমেলো মানগুলির সাথে কিছু করার জন্য আরও কিছু কোড রয়েছে, তাই অপ্টিমাইজার ক্লাস সম্পর্কে সমস্ত কিছুই সরিয়ে দেয় না কারণ এটি কিছুই করে না।

তারপরে আমাদের কাছে কোনও ফাংশন থেকে এই অবজেক্টগুলির মধ্যে একটি ফেরত দেওয়ার জন্য কিছু কোড রয়েছে এবং তারপরে অবজেক্টটি পুরোপুরি উপেক্ষা করা নয়, সত্যই অবজেক্টটি সত্যই তৈরি হয়েছে তা নিশ্চিত করার জন্য যোগফলটি ব্যবহার করুন। কমপক্ষে সাম্প্রতিক / আধুনিক সংকলকগুলির সাথে আমরা এটি চালানোর সময়, আমরা দেখতে পেলাম যে আমরা যে অনুলিপিটি লিখেছি সেগুলি কখনই চালায় না - এবং হ্যাঁ, আমি নিশ্চিত যে এটির সাথে একটি দ্রুত অনুলিপিও অনুলিপি করার shared_ptrচেয়ে ধীর হয় is মোটেই

সরানো আপনাকে এগুলি ব্যতীত সরাসরি (সরাসরি) না করতে পারার মতো ন্যায্য সংখ্যা করার অনুমতি দেয়। বাহ্যিক মার্জ সাজানোর "মার্জ" অংশটি বিবেচনা করুন - আপনার কাছে বলুন যে আপনি 8 টি ফাইল একত্রে মার্জ করতে যাচ্ছেন। আদর্শভাবে আপনি এই 8 টি ফাইল একটিতে রেখে দিতে চান vector- তবে যেহেতু vector(সি ++ 03 অনুযায়ী) উপাদানগুলি অনুলিপি করতে সক্ষম হওয়া দরকার, এবং ifstreamসেগুলি অনুলিপি করা যায় না, আপনি কিছু unique_ptr/ shared_ptr, বা সেটিকে কোনও ভেক্টরে রাখার জন্য সেই আদেশের কোনও কিছু। নোট করুন যে এমনকি (উদাহরণস্বরূপ) আমরা reserveস্পেস রেখেছি vectorতাই আমরা নিশ্চিত যে আমাদের ifstreamএসগুলি কখনই সত্যই অনুলিপি করা হবে না, সংকলক তা জানতে পারে না, সুতরাং কোডটি সংকলন করা হবে না যদিও আমরা জানি যে অনুলিপি নির্মাণকারী কখনও হবে না যাইহোক ব্যবহৃত।

যদিও এটি এখনও অনুলিপি করা যায় না, সি ++ 11 এ স্থানান্তরিত ifstream হতে পারে। এই ক্ষেত্রে, অবজেক্টগুলি সম্ভবত কখনও স্থানান্তরিত হবে না , তবে সত্য যে তারা প্রয়োজন হতে পারে সংকলককে খুশি রাখে, তাই আমরা কোনও স্মার্ট পয়েন্টার হ্যাক ছাড়াই আমাদের ifstreamঅবজেক্টগুলিকে vectorসরাসরি রাখতে পারি।

একটি ভেক্টর যে করে প্রসারিত এমন একটি সময় পদক্ষেপ শব্দার্থবিদ্যা সত্যিই হতে পারে একটি সুন্দর শালীন উদাহরণ / দরকারী যদিও হয়। এই ক্ষেত্রে, আরভিও / এনআরভিও সাহায্য করবে না, কারণ আমরা কোনও ফাংশন (বা খুব অনুরূপ কিছু) থেকে ফেরতের মান নিয়ে কাজ করছি না। আমাদের কাছে একটি ভেক্টর কিছু বস্তু ধারণ করে আছে এবং আমরা এই জিনিসগুলিকে মেমরির একটি নতুন, বৃহত্তর অংশে স্থানান্তরিত করতে চাই।

সি ++ তে, এটি নতুন স্মৃতিতে অবজেক্টগুলির অনুলিপি তৈরি করে, তারপরে পুরানো স্মৃতিতে পুরানো অবজেক্টগুলিকে ধ্বংস করে। পুরানোগুলি কেবল ফেলে দেওয়ার জন্য এই সমস্ত অনুলিপি তৈরি করা অবশ্য সময়ের অপচয় ছিল। সি ++ 11 এ আপনি পরিবর্তে এগুলি সরিয়ে নিয়ে যাওয়ার আশা করতে পারেন। এটি সাধারণত আমাদের সংক্ষেপে, একটি গভীর অনুলিপি (সাধারণত অনেক ধীর) এর পরিবর্তে অগভীর অনুলিপি করতে দেয়। অন্য কথায়, একটি স্ট্রিং বা ভেক্টর সহ (কেবলমাত্র কয়েকটি উদাহরণের জন্য) আমরা পয়েন্টারগুলিকে কেবলমাত্র বস্তুগুলিতে অনুলিপি করি, পরিবর্তে poin পয়েন্টারগুলি উল্লেখ করে সমস্ত ডেটার অনুলিপি তৈরি করে।


খুব বিস্তারিত ব্যাখ্যার জন্য ধন্যবাদ। যদি আমি সঠিকভাবে বুঝতে পারি তবে চলন্ত যে পরিস্থিতিতে চলে আসে সেগুলি স্বাভাবিক পয়েন্টারগুলি দ্বারা পরিচালনা করা যেতে পারে তবে প্রতিবার সমস্ত পয়েন্টার জাগলিং করে প্রোগ্রাম করা অনিরাপদ (জটিল এবং ত্রুটির প্রবণ) হবে be সুতরাং, পরিবর্তে, হুডের নীচে কিছু অনন্য_প্ট্র (বা অনুরূপ প্রক্রিয়া) রয়েছে এবং সরানো শব্দার্থবিজ্ঞানগুলি নিশ্চিত করে যে দিনের শেষে কেবলমাত্র কিছু পয়েন্টার অনুলিপি রয়েছে এবং কোনও বস্তু অনুলিপি করা হচ্ছে না।
জর্জিও

@ জর্জিও: হ্যাঁ, এটি বেশ সঠিক। ভাষা সত্যিই সরানো শব্দার্থবিজ্ঞান যুক্ত করে না; এটি মূল্যের উল্লেখগুলি যুক্ত করে। কোনও মূল্যের রেফারেন্স (স্পষ্টতই যথেষ্ট) কোনও মূল্যের সাথে আবদ্ধ হতে পারে, আপনি যদি জানেন যে এটি তথ্যের অভ্যন্তরীণ উপস্থাপনা "চুরি" করা নিরাপদ এবং একটি অনুলিপি না করে কেবল তার পয়েন্টারগুলি অনুলিপি করতে পারে।
জেরি কফিন

4

বিবেচনা:

vector<string> v;

ভিতে স্ট্রিং যুক্ত করার সময় এটি প্রয়োজনমতো প্রসারিত হবে এবং প্রতিটি পুনঃনির্ধারণের সাথে স্ট্রিংগুলি অনুলিপি করতে হবে। মুভ কনস্ট্রাক্টরগুলির সাথে এটি মূলত একটি ইস্যু নয়।

অবশ্যই আপনিও এর মতো কিছু করতে পারেন:

vector<unique_ptr<string>> v;

তবে এটি কার্যকরভাবে std::unique_ptrকার্যকর হবে কারণ প্রয়োগগুলি কনস্ট্রাক্টর মুভ করে।

std::shared_ptrআপনি যখন ভাগ করে নেওয়ার মালিকানা পেয়ে থাকেন তখনই (বিরল) পরিস্থিতিতে ব্যবহার করা অর্থবোধ করে।


তবে এর পরিবর্তে stringআমাদের Fooযেখানে এটির 30 টি ডেটা সদস্য রয়েছে তার উদাহরণ রয়েছে ? unique_ptrসংস্করণ আরও দক্ষ হবে না?
ভ্যাসিলিস

2

রিটার্ন মানগুলি যেখানে আমি প্রায়শই কোনও ধরণের রেফারেন্সের পরিবর্তে মান দ্বারা পাস করতে চাই। বিশাল পারফরম্যান্স পেনাল্টি ছাড়াই 'স্ট্যাক অন' কোনও বস্তু দ্রুত ফিরিয়ে দিতে সক্ষম হওয়াই দুর্দান্ত। অন্যদিকে, এটি কাছাকাছি পৌঁছানো বিশেষত কঠিন নয় (ভাগ করে নেওয়া পয়েন্টারগুলি ব্যবহার করা কেবল এত সহজ ...) সুতরাং আমি নিশ্চিত না যে এটি করতে সক্ষম হওয়ার জন্য আমার অবজেক্টগুলিতে অতিরিক্ত কাজ করা সত্যই মূল্যবান।


আমি সাধারণত কোনও ফাংশন / পদ্ধতি থেকে ফিরে আসা অবজেক্টগুলিকে মোড়ানোর জন্য স্মার্ট পয়েন্টারগুলিও ব্যবহার করি।
জর্জিও

1
@ জর্জিও: এটি অবশ্যই অনড় ও ধীর।
ডেড এমএমজি

আধুনিক সংকলকগণ যদি একটি সহজ অন-স্ট্যাক অবজেক্টটি ফিরিয়ে দেয় তবে একটি স্বয়ংক্রিয় পদক্ষেপ নেওয়া উচিত, সুতরাং ভাগ করা পিটিআর ইত্যাদির প্রয়োজন নেই
খ্রিস্টান সেভেরিন
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.