যুক্তি ফরোয়ার্ড করার জন্য কখন std :: ব্যবহার করবেন?


155

সি ++ 0x ব্যবহারের একটি উদাহরণ দেখায় std::forward:

template<class T>
void foo(T&& arg) 
{
  bar(std::forward<T>(arg));
}

std::forwardসর্বদা ব্যবহার করা কখন সুবিধাজনক ?

এছাড়াও, এটি &&পরামিতি ঘোষণায় ব্যবহার করা প্রয়োজন, এটি কি সব ক্ষেত্রেই বৈধ? আমি ভেবেছিলাম যদি কোনও ফাংশনটি দিয়ে এটি ঘোষিত হয় তবে আপনাকে কোনও অস্থায়ী অধ্যায়গুলি পাস &&করতে হবে, সুতরাং কোনও পরামিতি দিয়ে ফুও বলা যেতে পারে?

সবশেষে, আমার যদি কোনও ফাংশন কল থাকে যেমন:

template<int val, typename... Params>
void doSomething(Params... args) {
  doSomethingElse<val, Params...>(args...);
}

পরিবর্তে আমি এটি ব্যবহার করা উচিত:

template<int val, typename... Params>
void doSomething(Params&&... args) {
  doSomethingElse<val, Params...>(std::forward<Params>(args)...);
}

এছাড়াও, যদি ফাংশনে দু'বার প্যারামিটারগুলি ব্যবহার করা হয়, অর্থাৎ একই সাথে দুটি ফাংশনে ফরোয়ার্ড করা হয় তবে এটি ব্যবহার করা std::forwardকি বুদ্ধিমানের কাজ ? হবে না std::forwardদুইবার একটি অস্থায়ী একই জিনিস রূপান্তর, মেমরি চলন্ত এবং এটি একটি দ্বিতীয় ব্যবহারের জন্য অবৈধ করতে? নিম্নলিখিত কোডটি কি ঠিক আছে:

template<int val, typename... Params>
void doSomething(Params&&... args) {
  doSomethingElse<val, Params...>(std::forward<Params>(args)...);
  doSomethingWeird<val, Params...>(std::forward<Params>(args)...);
}

আমি কিছুটা বিভ্রান্ত হয়েছি std::forward, এবং আমি আনন্দের সাথে কিছু ক্লিয়ারিং ব্যবহার করব।

উত্তর:


124

এটি আপনার প্রথম উদাহরণের মতো ব্যবহার করুন:

template <typename T> void f(T && x)
{
  g(std::forward<T>(x));
}

template <typename ...Args> void f(Args && ...args)
{
  g(std::forward<Args>(args)...);
}

যে কারণে এর রেফারেন্স নিয়ম ধ্বসে : যদি T = U&, তারপর T&& = U&, কিন্তু যদি T = U&&, তারপর T&& = U&&, ফলে আপনি সর্বদা ফাংশন বডির ভিতরে সঠিক টাইপ দিয়ে শেষ। শেষ forwardঅবধি , আপনার ল্যাভ্যালু-পরিণত x(কারণ এটির এখন একটি নাম আছে!) এটি প্রাথমিকভাবে যদি একটি হয় তবে তাকে একটি মূল্যের রেফারেন্সে পরিণত করতে হবে।

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


আমি ভেবেছিলাম Args...&& args?
কুকুরছানা

5
@ ডিএডএমজি: এটি সর্বদা সঠিক যেটি আমি ভুলভাবে ছড়িয়ে দিয়েছি :-) নয় ... যদিও আমি মনে করি এটি সঠিকভাবে ভুলভাবে ছড়িয়েছে!
কেরেরেক এসবি

1
কিন্তু জেনেরিক টাইপ টি-এর জন্য কীভাবে ঘোষিত হয়?
এমকে

@MK। g আপনি যে প্যারামিটারগুলি চান তা নিয়মিত ফাংশন হিসাবে ঘোষণা করা হয়।
কফডেভলপার

1
@ সিএমডিএলপি: আপনি ঠিক বলেছেন যে এটি বারবার ফরওয়ার্ড করার জন্য সংজ্ঞায়িত, তবে এটি আপনার প্রোগ্রামের জন্য খুব কমই শব্দার্থগতভাবে সঠিক। যদিও সামনে অগ্রসর হওয়া সদস্যদের নেওয়া একটি দরকারী কেস। আমি উত্তর আপডেট করব।
কেরেক এসবি

4

কেরেকের উত্তর খুব দরকারী, তবে শিরোনাম থেকে এই প্রশ্নের পুরোপুরি উত্তর দেয় না:

যুক্তি ফরোয়ার্ড করার জন্য কখন std :: ব্যবহার করবেন?

এর উত্তর দেওয়ার জন্য আমাদের প্রথমে সর্বজনীন রেফারেন্সের ধারণাটি প্রবর্তন করা উচিত । স্কট মায়ার্স এই নাম দিয়েছিল এবং আজকাল তাদের প্রায়শই ফরোয়ার্ডিং রেফারেন্স বলা হয়। মূলত, আপনি যখন এই জাতীয় কিছু দেখেন:

template<typename T>
void f(T&& param);

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

এখন মূল প্রশ্নের উত্তর দেওয়া তুলনামূলকভাবে সহজ - প্রয়োগ করুন std::forward করুন:

  • একটি সর্বজনীন রেফারেন্স এটি ফাংশনে শেষবার ব্যবহার করা হয়েছে
  • একটি সর্বজনীন রেফারেন্স ফাংশন থেকে প্রত্যাবর্তন করা হচ্ছে যা মান অনুসারে ফিরে আসে

প্রথম মামলার উদাহরণ:

template<typename T>
void foo(T&& prop) {
    other.set(prop); // use prop, but don't modify it because we still need it
    bar(std::forward<T>(prop)); // final use -> std::forward
}

উপরের কোডে, আমরা শেষ করার propপরে কিছু অজানা মান রাখতে চাই না other.set(..), সুতরাং এখানে কোনও ফরোয়ার্ডিং ঘটে না। যাইহোক, কল করার সময় যখন আমরা এটি সম্পন্ন করেছি তেমন barফরোয়ার্ডpropbar যাই হোক না কেন তখন তার সঙ্গে চায় কি করতে পারেন (যেমন এটিকে সরান)।

দ্বিতীয় মামলার উদাহরণ:

template<typename T>
Widget transform(T&& prop) {
   prop.transform();
   return std::forward<T>(prop);
}

এই ফাংশন টেমপ্লেটটি propযদি কোনও মূল্য থাকে তবে তা ফেরতের মানের মধ্যে চলে যেতে হবে এবং যদি এটি কোনও মূল্য থাকে তবে এটি অনুলিপি করা উচিত। যদি আমরা std::forwardশেষের দিকে বাদ দিয়ে থাকি তবে আমরা সর্বদা একটি অনুলিপি তৈরি করতাম, যা propমূল্যায়ন হওয়ার পরে আরও ব্যয়বহুল ।

* সম্পূর্ণরূপে সুনির্দিষ্টভাবে বলতে গেলে, সার্বজনীন রেফারেন্স হল সিভি-অযোগ্য টেম্পলেট প্যারামিটারের সাথে একটি মূল রেফারেন্স নেওয়ার ধারণা।


0

এই উদাহরণ সাহায্য করে? আমি স্ট্যান্ড :: ফরওয়ার্ডের একটি দরকারী অ জেনেরিক উদাহরণ খুঁজে পেতে লড়াই করেছি, তবে একটি ব্যাংক অ্যাকাউন্টের একটি উদাহরণকে আমরা আঘাত করে যা নগদ বরাবর একটি যুক্তি হিসাবে জমা করার জন্য দিয়েছি।

সুতরাং যদি আমাদের কোনও অ্যাকাউন্টের কনস্টের সংস্করণ থাকে তবে আমরা যখন আমাদের ডিপোজিট টেমপ্লেটে এটি পাস করি তখন আমাদের আশা করা উচিত যে কনস্ট ফাংশন বলা হয়; এবং এটি তখন একটি ব্যতিক্রম ছোঁড়ে (ধারণাটি এটি লক করা অ্যাকাউন্ট ছিল!)

আমাদের যদি কোনও নিরপেক্ষ অ্যাকাউন্ট থাকে তবে আমাদের অ্যাকাউন্টটি সংশোধন করতে সক্ষম হওয়া উচিত।

#include <iostream>
#include <string>
#include <sstream> // std::stringstream
#include <algorithm> // std::move
#include <utility>
#include <iostream>
#include <functional>

template<class T> class BankAccount {
private:
    const T no_cash {};
    T cash {};
public:
    BankAccount<T> () {
        std::cout << "default constructor " << to_string() << std::endl;
    }
    BankAccount<T> (T cash) : cash (cash) {
        std::cout << "new cash " << to_string() << std::endl;
    }
    BankAccount<T> (const BankAccount& o) {
        std::cout << "copy cash constructor called for " << o.to_string() << std::endl;
        cash = o.cash;
        std::cout << "copy cash constructor result is  " << to_string() << std::endl;
    }
    // Transfer of funds?
    BankAccount<T> (BankAccount<T>&& o) {
        std::cout << "move cash called for " << o.to_string() << std::endl;
        cash = o.cash;
        o.cash = no_cash;
        std::cout << "move cash result is  " << to_string() << std::endl;
    }
    ~BankAccount<T> () {
        std::cout << "delete account " << to_string() << std::endl;
    }
    void deposit (const T& deposit) {
        cash += deposit;
        std::cout << "deposit cash called " << to_string() << std::endl;
    }
    friend int deposit (int cash, const BankAccount<int> &&account) {
        throw std::string("tried to write to a locked (const) account");
    }
    friend int deposit (int cash, const BankAccount<int> &account) {
        throw std::string("tried to write to a locked (const) account");
    }
    friend int deposit (int cash, BankAccount<int> &account) {
        account.deposit(cash);
        return account.cash;
    }
    friend std::ostream& operator<<(std::ostream &os, const BankAccount<T>& o) {
        os << "$" << std::to_string(o.cash);
        return os;
    }
    std::string to_string (void) const {
        auto address = static_cast<const void*>(this);
        std::stringstream ss;
        ss << address;
        return "BankAccount(" + ss.str() + ", cash $" + std::to_string(cash) + ")";
    }
};

template<typename T, typename Account>
int process_deposit(T cash, Account&& b) {
    return deposit(cash, std::forward<Account>(b));
}

int main(int, char**)
{
    try {
        // create account1 and try to deposit into it
        auto account1 = BankAccount<int>(0);
        process_deposit<int>(100, account1);
        std::cout << account1.to_string() << std::endl;
        std::cout << "SUCCESS: account1 deposit succeeded!" << std::endl;
    } catch (const std::string &e) {
        std::cerr << "FAILED: account1 deposit failed!: " << e << std::endl;
    }

    try {
        // create locked account2 and try to deposit into it; this should fail
        const auto account2 = BankAccount<int>(0);
        process_deposit<int>(100, account2);
        std::cout << account2.to_string() << std::endl;
        std::cout << "SUCCESS: account2 deposit succeeded!" << std::endl;
    } catch (const std::string &e) {
        std::cerr << "FAILED: account2 deposit failed!: " << e << std::endl;
    }

    try {
        // create locked account3 and try to deposit into it; this should fail
        auto account3 = BankAccount<int>(0);
        process_deposit<int>(100, std::move(account3));
        std::cout << account3.to_string() << std::endl;
        std::cout << "SUCCESS: account3 deposit succeeded!" << std::endl;
    } catch (const std::string &e) {
        std::cerr << "FAILED: account3 deposit failed!: " << e << std::endl;
    }
}

নির্মাণের জন্য:

cd std_forward
rm -f *.o example
c++ -std=c++2a -Werror -g -ggdb3 -Wall -c -o main.o main.cpp
c++ main.o  -o example
./example

প্রত্যাশিত আউটপুট:

# create account1 and try to deposit into it
new cash BankAccount(0x7ffee68d96b0, cash $0)
deposit cash called BankAccount(0x7ffee68d96b0, cash $100)
BankAccount(0x7ffee68d96b0, cash $100)
# SUCCESS: account1 deposit succeeded!
delete account BankAccount(0x7ffee68d96b0, cash $100)

# create locked account2 and try to deposit into it; this should fail
new cash BankAccount(0x7ffee68d9670, cash $0)
delete account BankAccount(0x7ffee68d9670, cash $0)
# FAILED: account2 deposit failed!: tried to write to a locked (const) account

# create locked account3 and try to deposit into it; this should fail
new cash BankAccount(0x7ffee68d9630, cash $0)
delete account BankAccount(0x7ffee68d9630, cash $0)
# FAILED: account3 deposit failed!: tried to write to a locked (const) account
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.