স্ট্যান্ড :: ফরোয়ার্ড ব্যবহারের মূল উদ্দেশ্যগুলি কী এবং এটি কোন সমস্যার সমাধান করে?


438

নিখুঁত ফরোয়ার্ডিংয়ে, std::forwardনামযুক্ত মূল্যসূচক রেফারেন্সগুলি t1এবং t2নামবিহীন মূল্যবোধের রেফারেন্সগুলিতে রূপান্তর করতে ব্যবহৃত হয় । এটা করার উদ্দেশ্য কী? innerযদি আমরা t1& t2lvue হিসাবে ছেড়ে যায় তবে কীভাবে এটি ডাকা ফাংশনকে প্রভাবিত করবে ?

template <typename T1, typename T2>
void outer(T1&& t1, T2&& t2) 
{
    inner(std::forward<T1>(t1), std::forward<T2>(t2));
}

উত্তর:


789

আপনাকে ফরওয়ার্ডিং সমস্যাটি বুঝতে হবে। আপনি পুরো সমস্যাটি বিশদে পড়তে পারেন , তবে আমি সংক্ষেপে বলব।

মূলত, এক্সপ্রেশন দেওয়া E(a, b, ... , c), আমরা অভিব্যক্তি f(a, b, ... , c)সমমান হতে চান । C ++ 03 এ এটি অসম্ভব। অনেক চেষ্টা আছে, কিন্তু তারা সব কিছু ক্ষেত্রে ব্যর্থ হয়।


সবচেয়ে সহজ হল একটি ল্যাভেলু-রেফারেন্স ব্যবহার করা:

template <typename A, typename B, typename C>
void f(A& a, B& b, C& c)
{
    E(a, b, c);
}

তবে এটি অস্থায়ী মানগুলি হ্যান্ডেল করতে ব্যর্থ হয়: f(1, 2, 3);কারণ সেগুলি কোনও মূল্য-রেফারেন্সের সাথে আবদ্ধ হতে পারে না।

পরবর্তী প্রচেষ্টা হতে পারে:

template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c)
{
    E(a, b, c);
}

যা উপরের সমস্যাটি সমাধান করে তবে ফ্লপ ফ্লপ করে। এটি এখন Eঅবিচ্ছিন্ন তর্ক করার অনুমতি দিতে ব্যর্থ :

int i = 1, j = 2, k = 3;
void E(int&, int&, int&); f(i, j, k); // oops! E cannot modify these

তৃতীয় প্রচেষ্টা const-রেফারেন্স গ্রহণ, কিন্তু তারপর const_cast's constদূরে:

template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c)
{
    E(const_cast<A&>(a), const_cast<B&>(b), const_cast<C&>(c));
}

এটি সমস্ত মানকে স্বীকার করে, সমস্ত মানকে অতিক্রম করতে পারে, তবে সম্ভাব্যভাবে অনির্ধারিত আচরণের দিকে পরিচালিত করে:

const int i = 1, j = 2, k = 3;
E(int&, int&, int&); f(i, j, k); // ouch! E can modify a const object!

একটি চূড়ান্ত সমাধান সবকিছু সঠিকভাবে পরিচালনা করে ... রক্ষণাবেক্ষণ করা অসম্ভব হয়ে ওঠে। কনস্ট এবং নন-কনস্টের সমস্ত সংমিশ্রণ fসহ আপনি ওভারলোডগুলি সরবরাহ করেন :

template <typename A, typename B, typename C>
void f(A& a, B& b, C& c);

template <typename A, typename B, typename C>
void f(const A& a, B& b, C& c);

template <typename A, typename B, typename C>
void f(A& a, const B& b, C& c);

template <typename A, typename B, typename C>
void f(A& a, B& b, const C& c);

template <typename A, typename B, typename C>
void f(const A& a, const B& b, C& c);

template <typename A, typename B, typename C>
void f(const A& a, B& b, const C& c);

template <typename A, typename B, typename C>
void f(A& a, const B& b, const C& c);

template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c);

এন আর্গুমেন্টগুলিতে 2 এন সংমিশ্রণ প্রয়োজন, একটি দুঃস্বপ্ন। আমরা এটি স্বয়ংক্রিয়ভাবে করতে চাই।

(এটি কার্যকরভাবে আমরা সি ++ 11 এ আমাদের জন্য সংকলকটি পেতে পারি))


সি ++ 11 এ আমরা এটি ঠিক করার সুযোগ পাই। একটি সমাধান বিদ্যমান ধরণের উপর টেমপ্লেট ছাড়ের নিয়মগুলিকে সংশোধন করে, তবে এটি সম্ভাব্যভাবে কোডের একটি বিশাল চুক্তি ভঙ্গ করে। সুতরাং আমাদের আর একটি উপায় খুঁজে বের করতে হবে।

সমাধানটি পরিবর্তে সদ্য যুক্ত হওয়া মূল্য-রেফারেন্সগুলি ব্যবহার করা হয় ; মূল্য-রেফারেন্স প্রকারগুলি হ্রাস করার সময় আমরা নতুন বিধিগুলি প্রবর্তন করতে পারি এবং যে কোনও পছন্দসই ফলাফল তৈরি করতে পারি। সর্বোপরি, আমরা সম্ভবত এখন কোড ভাঙতে পারি না।

যদি কোনও রেফারেন্সকে একটি রেফারেন্স দেওয়া হয় (দ্রষ্টব্য রেফারেন্সটি উভয় T&এবং এর অর্থ একটি অন্তর্ভুক্ত শব্দ T&&), ফলাফলটি খুঁজে বের করার জন্য আমরা নিম্নলিখিত নিয়মটি ব্যবহার করি:

"[প্রদত্ত] একটি টাইপ টিআর যা টাইপ টি-এর একটি রেফারেন্স," সিভিআর টিআর-র লভ্যালু রেফারেন্স "টাইপ তৈরির প্রয়াস" টিতে লভ্যালু রেফারেন্স "টাইপ তৈরি করে, যখন টাইপটি" রেভ্যালু রেফারেন্স "টাইপ তৈরি করার চেষ্টা করে সিভি টিআর "টিআর টাইপ তৈরি করে।"

বা সারণী আকারে:

TR   R

T&   &  -> T&  // lvalue reference to cv TR -> lvalue reference to T
T&   && -> T&  // rvalue reference to cv TR -> TR (lvalue reference to T)
T&&  &  -> T&  // lvalue reference to cv TR -> lvalue reference to T
T&&  && -> T&& // rvalue reference to cv TR -> TR (rvalue reference to T)

এরপরে, টেমপ্লেট আর্গুমেন্ট ছাড়ের সাথে: যদি একটি আর্গুমেন্টটি লভ্যালু এ হয় তবে আমরা টেমপ্লেট যুক্তিটি এ এর ​​একটি লভ্যালু রেফারেন্স সরবরাহ করি অন্যথায়, আমরা সাধারণত ছাড়াই। এটি তথাকথিত সর্বজনীন রেফারেন্স দেয় ( ফরোয়ার্ডিং রেফারেন্স শব্দটি এখন সরকারী one

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

কোডে:

template <typename T>
void deduce(T&& x); 

int i;
deduce(i); // deduce<int&>(int& &&) -> deduce<int&>(int&)
deduce(1); // deduce<int>(int&&)

শেষ জিনিসটি ভেরিয়েবলের মান বিভাগ "ফরোয়ার্ড" করা। মনে রাখবেন, একবার ফাংশনের ভিতরে প্যারামিটারটি কোনও কিছুর মূল্য হিসাবে পাস হতে পারে:

void foo(int&);

template <typename T>
void deduce(T&& x)
{
    foo(x); // fine, foo can refer to x
}

deduce(1); // okay, foo operates on x which has a value of 1

এটা কোন ভাল। E এর মতো ধরণের মান-বিভাগ হওয়া আমাদের দরকার! সমাধানটি হ'ল:

static_cast<T&&>(x);

এটি কি করে? বিবেচনা করুন আমরা deduceফাংশনটির ভিতরে রয়েছি, এবং আমাদের একটি লভালু পাস হয়েছে। এর অর্থ Tহ'ল একটি A&, এবং তাই স্থির castালাইয়ের জন্য লক্ষ্য প্রকারটি A& &&, বা ঠিক A&। যেহেতু xইতিমধ্যে একটি A&, তাই আমরা কিছুই করি না এবং একটি লভ্যালু রেফারেন্স রেখেই চলেছি।

যখন আমাদের কোনও মূল্য পাস হয়ে যায়, তখন Tহয় A, সুতরাং স্থির কাস্টের জন্য লক্ষ্য প্রকারটি A&&। কাস্টের ফলে একটি মূল্যবোধের অভিব্যক্তি হয়, যা আর কোনও মূল্যমানের রেফারেন্সে পাস করা যায় না । আমরা প্যারামিটারটির মান বিভাগটি বজায় রেখেছি।

এগুলি একসাথে রাখলে আমাদের "নিখুঁত ফরওয়ার্ডিং" পাওয়া যায়:

template <typename A>
void f(A&& a)
{
    E(static_cast<A&&>(a)); 
}

যখন fএকটি কদর প্রাপ্ত হয় E, একটি মূল্য পাওয়া যায়। যখন fএকটি মূল্যবোধ প্রাপ্ত হয় E, একটি মূল্যায়ন পায়। পারফেক্ট।


এবং অবশ্যই আমরা কুরুচি থেকে মুক্তি পেতে চাই। static_cast<T&&>মনে রাখা ক্রিপ্টিক এবং অদ্ভুত; এর পরিবর্তে একটি ইউটিলিটি ফাংশন তৈরি করা যাক forwardযা একই কাজ করে:

std::forward<A>(a);
// is the same as
static_cast<A&&>(a);

1
fএকটি ফাংশন হবে না , এবং একটি অভিব্যক্তি না?
মাইকেল ফৌকারাকিস

1
সমস্যার বিবৃতি সম্পর্কিত আপনার শেষ চেষ্টাটি সঠিক নয়: এটি কনস্ট্যান্ট মানগুলিকে নন-কনস্ট্যান্ট হিসাবে ফরোয়ার্ড করবে, এইভাবে মোটেও ফরওয়ার্ডিং নয়। এছাড়াও মনে রাখবেন যে প্রথম প্রয়াসে, const int iস্বীকৃতি দেওয়া হবে: Aহ্রাস করা হয় const int। ব্যর্থতাগুলি মূল্যবোধের আক্ষরিক জন্য। এছাড়াও নোট করুন যে কল করার জন্য deduced(1), এক্স int&&, নয় int(নিখুঁত ফরওয়ার্ডিং কখনই একটি অনুলিপি তৈরি করে না, যেমনটি xবাই-ভ্যালু প্যারামিটার হলে হবে)। নিছক Tহয় intxফরোয়ার্ডারে কোনও মূল্যকে মূল্যায়ন করার কারণটি হল নামযুক্ত মূল্যসূত্রগুলি লভালু এক্সপ্রেশন হয়ে যায়।
জোহানেস স্কাউব -

5
ব্যবহার forwardবা moveএখানে ব্যবহারের কোন পার্থক্য আছে? নাকি এটি কেবল একটি অর্থগত পার্থক্য?
0x499602D2

28
@ ডেভিড: std::moveসুস্পষ্ট টেম্পলেট আর্গুমেন্ট ছাড়াই ডাকা উচিত এবং সর্বদা ফলাফলের ফলস্বরূপ হয়, অন্যথায় std::forwardশেষ হতে পারে। std::moveযখন আপনি জানেন যে আপনার আর মানটির প্রয়োজন নেই এবং এটিকে অন্য কোথাও যেতে চান, তখন ব্যবহার করুন যখন std::forwardআপনার ফাংশন টেম্পলেটে মানগুলি পাস হয়েছে according
GManNGG

5
প্রথমে কংক্রিটের উদাহরণ দিয়ে শুরু করার এবং সমস্যাটি উদ্বুদ্ধ করার জন্য ধন্যবাদ; খুব উপকারী!
শ্রীভাতসার

61

আমি মনে করি যে স্ট্যান্ডার্ড :: ফরোয়ার্ডের একটি ধারণামূলক কোড প্রয়োগকারী আলোচনায় যুক্ত করতে পারে এটি স্কট মেয়ার্স টক একটি কার্যকর সি ++ 11/14 স্যাম্পলারের একটি স্লাইড

ধারণাগত কোড বাস্তবায়ন স্ট্যান্ড :: ফরোয়ার্ড

moveকোড কার্যকারিতা হয় std::move। এই আলোচনার আগে এর জন্য একটি (কার্যকরী) বাস্তবায়ন রয়েছে। আমি std :: এর বাস্তবায়নটি libstdc ++ এ , ফাইল মুভিচ-এ, পাওয়া গিয়েছি, তবে এটি মোটেই শিক্ষণীয় নয়।

ব্যবহারকারীর দৃষ্টিকোণ থেকে এর অর্থ হ'ল এটি std::forwardকোনও শর্তসাপেক্ষে কোনও মূল্যকে to আমি যদি এমন একটি ফাংশন লিখছি যা কোনও প্যারামিটারে লভালু বা মূল্যকে প্রত্যাশা করে এবং এটি যদি কোনও মূল্য হিসাবে পাস হয় তবেই এটি একটি ফাংশন হিসাবে অন্য কোনও ফাংশনে পৌঁছে দিতে চায় useful আমি যদি std :: ফরওয়ার্ডে প্যারামিটারটি মোড় না করি তবে এটি সর্বদা একটি সাধারণ রেফারেন্স হিসাবে পাস করা হবে passed

#include <iostream>
#include <string>
#include <utility>

void overloaded_function(std::string& param) {
  std::cout << "std::string& version" << std::endl;
}
void overloaded_function(std::string&& param) {
  std::cout << "std::string&& version" << std::endl;
}

template<typename T>
void pass_through(T&& param) {
  overloaded_function(std::forward<T>(param));
}

int main() {
  std::string pes;
  pass_through(pes);
  pass_through(std::move(pes));
}

নিশ্চিতভাবেই, এটি প্রিন্ট করে

std::string& version
std::string&& version

কোডটি পূর্বে উল্লিখিত আলাপের একটি উদাহরণের ভিত্তিতে তৈরি। শুরু থেকে 15:00 এ স্লাইড 10।


2
আপনার দ্বিতীয় লিঙ্কটি কোথাও সম্পূর্ণ আলাদা দেখায়।
ফারাপ

33

নিখুঁত ফরোয়ার্ডিংয়ে, স্ট্যান্ড :: ফরোয়ার্ড নামকরণকৃত রেভালিউ রেফারেন্স টি 1 এবং টি 2 কে নামবিহীন মূল্যবোধের রেফারেন্সে রূপান্তর করতে ব্যবহৃত হয়। এটা করার উদ্দেশ্য কী? আমরা যদি t1 এবং t2 কে লভ্যালু হিসাবে ছেড়ে দিই তবে কীভাবে এটি ডাকা ফাংশনটিকে অভ্যন্তরীণভাবে প্রভাবিত করবে?

template <typename T1, typename T2> void outer(T1&& t1, T2&& t2) 
{
    inner(std::forward<T1>(t1), std::forward<T2>(t2));
}

আপনি যদি কোনও অভিব্যক্তিতে নামযুক্ত মূল্যসূচক রেফারেন্স ব্যবহার করেন তবে এটি আসলে একটি মূল্যবান (কারণ আপনি নামটির মাধ্যমে অবজেক্টটি উল্লেখ করেন)। নিম্নলিখিত উদাহরণ বিবেচনা করুন:

void inner(int &,  int &);  // #1
void inner(int &&, int &&); // #2

এখন, আমরা যদি outerএই মত কল

outer(17,29);

আমরা 17 এবং 29 কে # 2 এ ফরোয়ার্ড করতে চাই কারণ 17 এবং 29 সংখ্যার আক্ষরিক এবং এরূপ মূল্যবোধ। তবে যেহেতু t1এবং t2অভিব্যক্তিতে inner(t1,t2);লিভ্যালু রয়েছে, আপনি # 2 এর পরিবর্তে # 1 প্রার্থনা করছেন। এজন্য আমাদের উল্লেখগুলি নামবিহীন রেফারেন্সগুলির সাথে ফিরে যেতে হবে std::forward। সুতরাং, t1ইন outerসর্বদা একটি লভ্যালু এক্সপ্রেশন থাকে তবে forward<T1>(t1)তার উপর নির্ভর করে কোনও মূল্যের ভাব হতে পারে T1T1ল্যাভালু রেফারেন্স হলে পরবর্তীটি কেবল একটি লভ্যালু এক্সপ্রেশন । আর T1শুধুমাত্র ক্ষেত্রে একটি lvalue রেফারেন্স বাইরের প্রথম যুক্তি একটি lvalue অভিব্যক্তি ছিল হতে অনুমিত হয়।


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

11

যদি আমরা t1 & t2 কে লভ্যালু হিসাবে ছেড়ে দিই তবে এটি কীভাবে অভ্যন্তরীণ বলে তাকে প্রভাবিত করবে?

যদি, তাত্ক্ষণিকভাবে পরে, T1টাইপ করা হয় char, এবং T2একটি শ্রেণীর হয়, আপনি t1প্রতি অনুলিপি এবং t2প্রতি constরেফারেন্স পাস করতে চান । ঠিক আছে, যদি না inner()এগুলিকে অ- constরেফারেন্স অনুযায়ী গ্রহণ না করা হয়, তবে , সেই ক্ষেত্রে আপনিও এটি করতে চান।

বিভিন্ন ধরণের outer()ফাংশন লেখার চেষ্টা করুন যা এটির যথোপযুক্ত প্রসঙ্গ ছাড়াই এটি প্রয়োগ করে, inner()প্রকারের প্রকার থেকে আর্গুমেন্টগুলি পাস করার জন্য সঠিক পদ্ধতিটি ছাড়িয়ে । আমি মনে করি আপনার মধ্যে 2 ^ 2 এর কিছু দরকার, তর্কগুলি কাটাতে বেশ মোটা টেম্পলেট-মেটা স্টাফ এবং সমস্ত ক্ষেত্রে এই অধিকার পেতে অনেক সময়।

এবং তারপরে কেউ আসে inner()যা পয়েন্টার অনুসারে আর্গুমেন্ট নেয়। আমি মনে করি যে এখন 3 ^ 2 করে। (বা 4 ^ 2। হেইল, constপয়েন্টারটি কোনও পার্থক্য আনবে কিনা তা ভাবতে ভাবতে আমার বিরক্ত হওয়া যাবে না ))

এবং তারপরে কল্পনা করুন আপনি পাঁচটি পরামিতিগুলির জন্য এটি করতে চান। বা সাত।

এখন আপনি জানেন যে কেন কিছু উজ্জ্বল মনগুলি "নিখুঁত ফরোয়ার্ডিং" নিয়ে আসে: এটি সংকলকটি আপনার জন্য এটি করে তোলে।


5

একটি বিষয় যা স্ফটিক পরিষ্কার করা হয়নি তা হ'ল সঠিকভাবে static_cast<T&&>পরিচালনাও const T&করে।
কার্যক্রম:

#include <iostream>

using namespace std;

void g(const int&)
{
    cout << "const int&\n";
}

void g(int&)
{
    cout << "int&\n";
}

void g(int&&)
{
    cout << "int&&\n";
}

template <typename T>
void f(T&& a)
{
    g(static_cast<T&&>(a));
}

int main()
{
    cout << "f(1)\n";
    f(1);
    int a = 2;
    cout << "f(a)\n";
    f(a);
    const int b = 3;
    cout << "f(const b)\n";
    f(b);
    cout << "f(a * b)\n";
    f(a * b);
}

উত্পাদন:

f(1)
int&&
f(a)
int&
f(const b)
const int&
f(a * b)
int&&

নোট করুন যে 'চ' একটি টেম্পলেট ফাংশন হতে হবে। যদি এটি কেবল 'অকার্যকর এফ (ইন্ট অ্যান্ড এন্ড এ)' হিসাবে সংজ্ঞায়িত হয় তবে এটি কাজ করে না।


ভাল পয়েন্ট, সুতরাং টি অ্যান্ড & স্থির কাস্ট এছাড়াও রেফারেন্স ভেঙে নিয়ম অনুসরণ করে, তাই না?
বার্নে

3

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

     std::forward<int>(1);
     std::forward<std::string>("Hello");

আমার মতে, সরানো এবং এগিয়ে হ'ল নকশা নিদর্শন যা r- মান রেফারেন্স টাইপ প্রবর্তনের পরে প্রাকৃতিক ফলাফল। ভুল ব্যবহার নিষিদ্ধ না করা না হলে আমরা এটি সঠিকভাবে ব্যবহৃত হয়েছে বলে ধরে নেওয়া কোনও পদ্ধতির নাম রাখি না।


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

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