ตรรกะเบื้องหลังคำหลัก "ใช้" ใน C ++ คืออะไร?


155

ตรรกะเบื้องหลังคำหลัก "ใช้" ใน C ++ คืออะไร?

มันถูกใช้ในสถานการณ์ที่แตกต่างกันและฉันกำลังพยายามค้นหาว่าสิ่งเหล่านี้มีบางอย่างที่เหมือนกันหรือไม่และมีเหตุผลว่าทำไมจึงใช้คีย์เวิร์ด "ใช้" เช่นนี้

using namespace std; // to import namespace in the current namespace
using T = int; // type alias
using SuperClass::X; // using super class methods in derived class

56
คอมมิตีมาตรฐานเกลียดการแนะนำคำหลักใหม่ในไวยากรณ์ C ++
ทำจาก catz

4
@tehinternetsismadeofcatz ถ้าเป็นตรรกะจริงๆขอโทษทีฉันจะไปฆ่าตัวตายตอนนี้
user3111311

64
@ user3111311: คุณจำความหมายของการแนะนำคำสงวนใหม่ได้ใช่ไหม? หมายความว่าโค้ดที่มีอยู่ทั้งหมดที่ใช้เป็นชื่อตัวระบุนั้นไม่สามารถรวบรวมได้ในทันที นั่นเป็นสิ่งที่ไม่ดี ตัวอย่างเช่นมีรหัส C จำนวนมากที่ไม่สามารถรวบรวมเป็น C ++ ได้เนื่องจากมีสิ่งต่างๆเช่นint class;. มันจะแย่กว่านั้นถ้าโค้ด C ++ หยุดกะทันหันเป็น C ++ ที่ถูกต้อง
Ben Voigt

7
@BenVoigt: การใช้รหัส C int class;จะไม่คอมไพล์เนื่องจาก C ++ ไม่ใช่เรื่องเลวร้ายอย่างสิ้นเชิง สามารถใช้เพื่อรับประกันว่าโค้ด C จะถูกคอมไพล์เป็น C มันง่ายเกินไปที่จะลืมว่า C และ C ++ เป็นภาษาที่แตกต่างกันสองภาษาและในทางปฏิบัติมีรหัสที่เป็น C ที่ถูกต้องและ C ++ ที่ถูกต้อง แต่มีความหมายต่างกัน
Keith Thompson

3
ในส่วนที่usingไม่เลวร้ายยิ่ง static(หรือดีกว่า) IMHO ประเด็นของการไม่แนะนำคำหลักใหม่มีความสำคัญมากตามที่อธิบายโดย internets นั้นทำจาก catz และ Ben Voigt
Cassio Neri

คำตอบ:


120

ใน C ++ 11 usingคำหลักเมื่อนำมาใช้สำหรับการที่จะเหมือนกันtype aliastypedef

7.1.3.2

นอกจากนี้คุณยังสามารถใช้ชื่อ typedef ได้โดยการประกาศนามแฝง ตัวระบุต่อจากคีย์เวิร์ดที่ใช้จะกลายเป็น typedef-name และแอ็ตทริบิวต์ - specifier-seq ที่เป็นทางเลือกตามหลังตัวระบุจะผนวกเข้ากับ typedef-name นั้น มันมีความหมายเหมือนกับว่ามันถูกนำมาใช้โดยตัวระบุ typedef โดยเฉพาะอย่างยิ่งมันไม่ได้กำหนดประเภทใหม่และจะไม่ปรากฏใน type-id

Bjarne Stroustrup ให้ตัวอย่างที่ใช้ได้จริง:

typedef void (*PFD)(double);    // C style typedef to make `PFD` a pointer to a function returning void and accepting double
using PF = void (*)(double);    // `using`-based equivalent of the typedef above
using P = [](double)->void; // using plus suffix return type, syntax error
using P = auto(double)->void // Fixed thanks to DyP

Pre-C ++ 11 usingคีย์เวิร์ดสามารถนำฟังก์ชันสมาชิกเข้าสู่ขอบเขต ใน C ++ 11 ตอนนี้คุณสามารถทำสิ่งนี้สำหรับตัวสร้างได้แล้ว (ตัวอย่าง Bjarne Stroustrup อื่น):

class Derived : public Base { 
public: 
    using Base::f;    // lift Base's f into Derived's scope -- works in C++98
    void f(char);     // provide a new f 
    void f(int);      // prefer this f to Base::f(int) 

    using Base::Base; // lift Base constructors Derived's scope -- C++11 only
    Derived(char);    // provide a new constructor 
    Derived(int);     // prefer this constructor to Base::Base(int) 
    // ...
}; 

Ben Voight ให้เหตุผลที่ดีที่อยู่เบื้องหลังเหตุผลของการไม่แนะนำคำหลักใหม่หรือไวยากรณ์ใหม่ มาตรฐานต้องการหลีกเลี่ยงการทำลายรหัสเก่าให้มากที่สุด นี่คือเหตุผลที่อยู่ในเอกสารข้อเสนอของคุณจะเห็นส่วนที่ชอบImpact on the Standard, Design decisionsและวิธีการที่พวกเขาอาจจะส่งผลกระทบต่อรหัสเก่า มีบางสถานการณ์ที่ดูเหมือนว่าข้อเสนอจะเป็นความคิดที่ดี แต่อาจไม่มีแรงฉุดเพราะมันจะยากเกินไปที่จะนำไปใช้สับสนเกินไปหรืออาจขัดแย้งกับรหัสเดิม


นี่คือกระดาษเก่าจาก 2003 n1449 เหตุผลดูเหมือนจะเกี่ยวข้องกับเทมเพลต คำเตือน: อาจมีการพิมพ์ผิดเนื่องจากการคัดลอกจาก PDF

ก่อนอื่นให้พิจารณาตัวอย่างของเล่น:

template <typename T>
class MyAlloc {/*...*/};

template <typename T, class A>
class MyVector {/*...*/};

template <typename T>

struct Vec {
typedef MyVector<T, MyAlloc<T> > type;
};
Vec<int>::type p; // sample usage

ปัญหาพื้นฐานของสำนวนนี้และข้อเท็จจริงที่เป็นแรงจูงใจหลักสำหรับข้อเสนอนี้คือสำนวนทำให้พารามิเตอร์เทมเพลตปรากฏในบริบทที่ไม่สามารถอนุมานได้ นั่นคือจะไม่สามารถเรียกใช้ฟังก์ชัน foo ด้านล่างโดยไม่ระบุอาร์กิวเมนต์เทมเพลตอย่างชัดเจน

template <typename T> void foo (Vec<T>::type&);

ดังนั้นไวยากรณ์จึงค่อนข้างน่าเกลียด เราค่อนข้างจะหลีกเลี่ยงสิ่งที่ซ้อนกัน::type เราต้องการสิ่งต่อไปนี้:

template <typename T>
using Vec = MyVector<T, MyAlloc<T> >; //defined in section 2 below
Vec<int> p; // sample usage

โปรดทราบว่าเราหลีกเลี่ยงคำว่า "เทมเพลต typedef" โดยเฉพาะและแนะนำไวยากรณ์ใหม่ที่เกี่ยวข้องกับคู่ "ใช้" และ "=" เพื่อช่วยหลีกเลี่ยงความสับสน: เราไม่ได้กำหนดประเภทใด ๆ ที่นี่เราขอแนะนำคำพ้องความหมาย (เช่นนามแฝง) สำหรับ นามธรรมของ type-id (เช่นนิพจน์ประเภท) ที่เกี่ยวข้องกับพารามิเตอร์เทมเพลต หากใช้พารามิเตอร์เทมเพลตในบริบทที่อนุมานไม่ได้ในนิพจน์ชนิดเมื่อใดก็ตามที่ใช้นามแฝงเทมเพลตเพื่อสร้าง template-id ค่าของพารามิเตอร์เทมเพลตที่เกี่ยวข้องจะสามารถอนุมานได้ - จะมีข้อมูลเพิ่มเติมเกี่ยวกับสิ่งนี้ ไม่ว่าในกรณีใดตอนนี้คุณสามารถเขียนฟังก์ชันทั่วไปซึ่งทำงานVec<T>ในบริบทที่อนุมานได้และไวยากรณ์ก็ได้รับการปรับปรุงเช่นกัน ตัวอย่างเช่นเราสามารถเขียน foo ใหม่เป็น:

template <typename T> void foo (Vec<T>&);

เราเน้นที่นี่ว่าหนึ่งในเหตุผลหลักในการเสนอชื่อแทนเทมเพลตคือการหักอาร์กิวเมนต์และการเรียกร้องfoo(p) จะประสบความสำเร็จ


เอกสารติดตามผลn1489อธิบายสาเหตุที่usingแทนที่จะใช้typedef:

มีการแนะนำให้ (อีกครั้ง) ใช้คีย์เวิร์ด typedef - ดังที่ทำในเอกสาร [4] - เพื่อแนะนำชื่อแทนเทมเพลต:

template<class T> 
    typedef std::vector<T, MyAllocator<T> > Vec;

สัญกรณ์ดังกล่าวมีข้อดีของการใช้คำหลักที่รู้จักกันแล้วเพื่อแนะนำนามแฝงประเภท อย่างไรก็ตามมันยังแสดงความไม่พอใจหลายประการซึ่งทำให้เกิดความสับสนในการใช้คำหลักที่ทราบว่าใช้นามแฝงสำหรับชื่อชนิดในบริบทที่นามแฝงไม่ได้กำหนดประเภท แต่เป็นเทมเพลต Vecไม่ใช่นามแฝงสำหรับประเภทและไม่ควรนำมาใช้กับชื่อที่พิมพ์ผิด ชื่อVecนี้เป็นชื่อของตระกูลstd::vector< [bullet] , MyAllocator< [bullet] > > - โดยที่สัญลักษณ์แสดงหัวข้อย่อยเป็นตัวยึดสำหรับ type-name ดังนั้นเราจึงไม่เสนอไวยากรณ์ "typedef" ในทางกลับกันประโยค

template<class T>
    using Vec = std::vector<T, MyAllocator<T> >;

สามารถอ่าน / ตีความว่าเป็น: จากนี้ไปผมจะใช้เป็นคำพ้องสำหรับVec<T> std::vector<T, MyAllocator<T> >ด้วยการอ่านดังกล่าวไวยากรณ์ใหม่สำหรับนามแฝงดูเหมือนมีเหตุผล

ฉันคิดว่าความแตกต่างที่สำคัญเกิดขึ้นที่นี่นามแฝง es แทนที่จะเป็นประเภท s คำพูดอื่นจากเอกสารเดียวกัน:

การประกาศนามแฝงเป็นการประกาศไม่ใช่คำจำกัดความ นามแฝง - การประกาศแนะนำชื่อในพื้นที่ประกาศเป็นนามแฝงสำหรับประเภทที่กำหนดโดยด้านขวามือของการประกาศ แกนหลักของข้อเสนอนี้เกี่ยวข้องกับนามแฝงของชื่อประเภท แต่เห็นได้ชัดว่าสัญกรณ์สามารถนำไปใช้ในการสะกดแบบอื่นของเนมสเปซ - นามแฝงหรือชุดการตั้งชื่อของฟังก์ชันที่โอเวอร์โหลด (ดู✁ 2.3 สำหรับการสนทนาเพิ่มเติม) [ หมายเหตุของฉัน: ส่วนนั้นจะกล่าวถึงลักษณะของไวยากรณ์และเหตุผลว่าทำไมจึงไม่เป็นส่วนหนึ่งของข้อเสนอ ] อาจสังเกตได้ว่าการประกาศนามแฝงการผลิตไวยากรณ์เป็นที่ยอมรับได้ทุกที่ที่ยอมรับการประกาศ typedef หรือการกำหนดเนมสเปซนามแฝง

สรุปสำหรับบทบาทของusing:

  • ชื่อแทนเทมเพลต (หรือ template typedefs เดิมเป็นชื่อที่ต้องการ)
  • นามแฝงเนมสเปซ (เช่นnamespace PO = boost::program_optionsและusing PO = ...เทียบเท่า)
  • A typedef declaration can be viewed as a special case of non-template alias-declarationเอกสารกล่าวว่า เป็นการเปลี่ยนแปลงด้านสุนทรียศาสตร์และถือว่าเหมือนกันในกรณีนี้
  • การนำบางสิ่งเข้าสู่ขอบเขต (ตัวอย่างเช่นnamespace stdในขอบเขตส่วนกลาง) ฟังก์ชันสมาชิกการสืบทอดตัวสร้าง

มันไม่สามารถนำมาใช้สำหรับ:

int i;
using r = i; // compile-error

แทนที่จะทำ:

using r = decltype(i);

การตั้งชื่อชุดโอเวอร์โหลด

// bring cos into scope
using std::cos;

// invalid syntax
using std::cos(double);

// not allowed, instead use Bjarne Stroustrup function pointer alias example
using test = std::cos(double);

2
@ user3111311 คุณคิดคำหลักอะไรอีกบ้าง? "อัตโนมัติ"? "จดทะเบียน"?
Raymond Chen

3
using P = [](double)->void;คือ AFAIK ไม่ใช่ C ++ 11 ที่ถูกต้อง อย่างไรก็ตามนี่คือ: using P = auto(double)->void;และสร้างประเภทฟังก์ชัน (เช่นP*ตัวชี้ฟังก์ชัน)
dyp

2
ชื่อของเขาคือ Bjarne Stroustrup;) (สังเกต r ตัวที่สองใน Stroustrup)
dyp

1
@RaymondChen: จริงๆแล้วregisterก็ไม่ได้ฟังดูแย่ขนาดนั้นอยู่:register X as Y
MFH

1
น่าเสียดายที่registerเริ่มต้นการประกาศตัวแปรดังนั้นสิ่งนี้จึงมีความหมาย ประกาศตัวแปรรีจิสเตอร์ชื่อ Y ประเภท X
Raymond Chen
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.