ฉันสามารถใช้สมาชิกประเภท "ตนเอง" แบบอิสระใน C ++ ได้หรือไม่


101

C ++ ไม่มีคีย์เวิร์ดที่เทียบเท่าของPHPselfซึ่งประเมินเป็นประเภทของคลาสปิดล้อม

ง่ายพอที่จะปลอมเป็นรายชั้น:

struct Foo
{
   typedef Foo self;
};

แต่ฉันต้องเขียนFooอีกครั้ง บางทีวันหนึ่งฉันอาจจะเข้าใจผิดและทำให้เกิดข้อผิดพลาด

ฉันสามารถใช้ชุดค่าผสมdecltypeและเพื่อนเพื่อทำให้งานนี้เป็นแบบ "อัตโนมัติ" ได้หรือไม่ ฉันลองทำสิ่งต่อไปนี้แล้วแต่thisใช้ไม่ได้ในที่นั้น:

struct Foo
{
   typedef decltype(*this) self;
};

// main.cpp:3:22: error: invalid use of 'this' at top level
//     typedef decltype(*this) self;

(ฉันจะไม่กังวลเกี่ยวกับการเทียบเท่าstaticซึ่งจะเหมือนกัน แต่มีผลผูกพันในช่วงปลาย)


9
this_tน่าจะสอดคล้องกับการตั้งชื่อ C ++ ปกติมากกว่า
Bartek Banachewicz

3
@BartekBanachewicz: หรือ this_type
PlasmaHH

10
@ Praetorian ฉันจำไม่ได้ว่าเป็นข้อเสนอหรือไม่ แต่มีคนแนะนำauto()และ~auto()สำหรับ ctors / dtors น่าสนใจที่จะพูดน้อยที่สุด ถ้าใช้เพื่อจุดประสงค์นั้นอาจจะเป็นtypedef auto self;ไปได้ แต่ดูเหมือนว่าจะไม่ชัดเจนสำหรับฉัน
chris

11
สุจริตถ้าผมจะให้คำแนะนำในไวยากรณ์จะทำให้เรื่องนี้เป็นไปได้ก็อาจจะเป็นdecltype(class)อาจจะมีdecltype(struct)เทียบเท่า นั่นเป็นที่ชัดเจนมากขึ้นกว่าเพียงแค่autoในบริบทที่เฉพาะเจาะจงและฉันไม่เห็นปัญหาใด ๆ decltype(auto)กับมันเหมาะสมในภาษาที่อยู่บนพื้นฐานของ
chris

11
เนื่องจากคุณต้องการหลีกเลี่ยงข้อผิดพลาดคุณสามารถตั้งค่าฟังก์ชันสมาชิกจำลองด้วย static_assert เช่นvoid _check() { static_assert(std::is_same<self&, decltype(*this)>::value, "Correct your self type"); }ไม่ทำงานกับเทมเพลตคลาสแม้ว่า ...
milleniumbug

คำตอบ:


39

นี่คือวิธีที่คุณสามารถทำได้โดยไม่ต้องทำซ้ำประเภทของ Foo:

template <typename...Ts>
class Self;

template <typename X, typename...Ts>
class Self<X,Ts...> : public Ts...
{
protected:
    typedef X self;
};

#define WITH_SELF(X) X : public Self<X>
#define WITH_SELF_DERIVED(X,...) X : public Self<X,__VA_ARGS__>

class WITH_SELF(Foo)
{
    void test()
    {
        self foo;
    }
};

หากคุณต้องการได้รับจากFooนั้นคุณควรใช้มาโครWITH_SELF_DERIVEDด้วยวิธีต่อไปนี้:

class WITH_SELF_DERIVED(Bar,Foo)
{
    /* ... */
};

คุณยังสามารถทำการสืบทอดหลาย ๆ คลาสโดยมีคลาสพื้นฐานได้มากเท่าที่คุณต้องการ (ขอบคุณเทมเพลตตัวแปรและมาโครตัวแปร):

class WITH_SELF(Foo2)
{
    /* ... */
};

class WITH_SELF_DERIVED(Bar2,Foo,Foo2)
{
    /* ... */
};

ฉันได้ตรวจสอบแล้วว่าใช้งานได้กับ gcc 4.8 และ clang 3.4


18
ฉันเดาว่าคำตอบคือ "ไม่ แต่ Ralph ทำได้!" ;)
Lightness Races ในวงโคจร

3
วิธีนี้ดีกว่าการใส่ typedef ลงในนั้นอย่างไร? และพระเจ้าทำไมคุณถึงต้องการตัวพิมพ์ดีดด้วย? ทำไม?
เส้นทางไมล์

7
@MilesRout นี่คือคำถามเกี่ยวกับคำถามไม่ใช่คำตอบ ในหลาย ๆ กรณีในการพัฒนาซอฟต์แวร์ (และโดยเฉพาะอย่างยิ่งการบำรุงรักษา) การหลีกเลี่ยงความซ้ำซ้อนในโค้ดจะเป็นประโยชน์เพื่อให้การเปลี่ยนแปลงบางอย่างในที่เดียวไม่จำเป็นต้องให้คุณเปลี่ยนรหัสในที่อื่น นั่นคือจุดรวมของautoและdecltypeหรือในกรณีselfนี้
Ralph Tandetzky

1
template<typename T>class Self{protected: typedef T self;}; class WITH_SELF(Foo) : public Bar, private Baz {};จะง่ายกว่าและจะช่วยให้สามารถควบคุมการถ่ายทอดทางพันธุกรรมได้อย่างแม่นยำมากขึ้น - มีเหตุผลใดบ้างที่ต่อต้าน?
Aconcagua

@mmmmmmmm ถ้าคุณยังไม่ได้เรียนรู้ที่จะเข้าใจหลักการ "Don't Repeat Yourself" อย่างลึกซึ้งมีโอกาสที่คุณจะเขียนโค้ดไม่เพียงพอ / จริงจัง "ความยุ่งเหยิง" นี้ (ห่างไกลจากความจริง) เป็นวิธีแก้ไขที่ดีทีเดียวในบริบทของการพูดถึงคุณลักษณะทางภาษาที่ไม่สง่างาม
Sz.

38

วิธีแก้ปัญหาที่เป็นไปได้ (เนื่องจากคุณยังต้องเขียนประเภทหนึ่งครั้ง):

template<typename T>
struct Self
{
protected:
    typedef T self;
};

struct Foo : public Self<Foo>
{
    void test()
    {
        self obj;
    }
};

สำหรับเวอร์ชันที่ปลอดภัยยิ่งขึ้นเราสามารถรับรองได้ว่าTมาจากSelf<T>:

Self()
{
    static_assert(std::is_base_of<Self<T>, T>::value, "Wrong type passed to Self");
}

โปรดสังเกตว่าstatic_assertฟังก์ชันภายในสมาชิกน่าจะเป็นวิธีเดียวในการตรวจสอบเนื่องจากประเภทที่ส่งผ่านไปstd::is_base_ofจะต้องสมบูรณ์


4
ไม่จำเป็นต้องมีtypenameใน typedef และเนื่องจากสิ่งนี้ไม่ได้ลดจำนวนความซ้ำซ้อนฉันจึงไม่คิดว่ามันเป็นทางเลือกที่ใช้ได้
Konrad Rudolph

มีปัญหาเดียวกันกับการFooตั้งชื่อซ้ำ
Bartek Banachewicz

6
แม้ว่าจะดีกว่าแนวทางเดิมเล็กน้อยเนื่องจากการทำซ้ำอยู่ใกล้กันมาก ไม่ใช่วิธีแก้ปัญหาสำหรับคำถาม แต่ +1 สำหรับความพยายามที่คุ้มค่าในกรณีที่ดีที่สุดวิธีแก้ปัญหา
Lightness Races ใน Orbit

4
ฉันใช้วิธีแก้ปัญหานั้นสองสามครั้งและมีสิ่งที่ไม่ดีอยู่อย่างหนึ่ง: เมื่อได้รับมาในภายหลังFooคุณต้อง: (1) เผยแพร่ T ขึ้นไปยังผู้สืบทอดใบไม้หรือ (2) อย่าลืมสืบทอดจาก SelfT หลายครั้ง หรือ (3) ยอมรับว่าเด็ก ๆ ทุกคนเป็นฐาน .. ใช้งานได้ แต่ไม่สวย
quetzalcoatl

@quetzalcoatl: เนื่องจากฉันพยายามทำซ้ำselfมากกว่าstaticนั้นก็ไม่มีปัญหา
Lightness Races ใน Orbit

33

คุณสามารถใช้มาโครแทนการประกาศคลาสปกติซึ่งจะทำเพื่อคุณ

#define CLASS_WITH_SELF(X) class X { typedef X self;

แล้วใช้ like

CLASS_WITH_SELF(Foo) 
};

#define END_CLASS }; น่าจะช่วยให้อ่านง่าย


คุณสามารถใช้ @ Paranaix Selfและใช้งานได้ (เริ่มแฮ็คจริงๆ)

#define WITH_SELF(X) X : public Self<X>

class WITH_SELF(Foo) {
};

18
EWWWW END_CLASS นั่นไม่จำเป็นเลย
ลูกสุนัข

31
@DeadMG ฉันคิดว่าบางคนอาจชอบความสม่ำเสมอมากกว่านี้ ท้ายที่สุดการใช้มาโครครั้งแรกไม่ได้ลงท้ายด้วย{ดังนั้นการ}"แขวน" ซึ่งโปรแกรมแก้ไขข้อความก็อาจไม่ชอบเช่นกัน
Bartek Banachewicz

6
เป็นความคิดที่ดี แต่แม้ว่าฉันจะไม่เห็นด้วยโดยพื้นฐานแล้วฉันจะยอมรับการใช้งานของมันที่นี่ถ้ามันเลียนแบบขอบเขต C ++ นั่นคือถ้ามันใช้งานได้CLASS_WITH_SELF(foo) { … };- และฉันคิดว่ามันเป็นไปไม่ได้ที่จะบรรลุ
Konrad Rudolph

2
@KonradRudolph ฉันได้เพิ่มวิธีการทำเช่นนั้นด้วย ไม่ใช่ว่าฉันชอบเพียงเพื่อความสมบูรณ์
Bartek Banachewicz

1
อย่างไรก็ตามมีปัญหาบางอย่างเกี่ยวกับแนวทางดังกล่าว อันดับแรกไม่อนุญาตให้คุณทำให้คลาสสืบทอดได้อย่างง่ายดาย (เว้นแต่คุณจะใช้พารามิเตอร์มาโครอื่น) และอย่างที่สองมีปัญหาทั้งหมดในการสืบทอดจากคลาสที่Selfมี
Bartek Banachewicz

31

ฉันไม่มีหลักฐานเชิงบวก แต่ฉันคิดว่ามันเป็นไปไม่ได้ สิ่งต่อไปนี้ล้มเหลวด้วยเหตุผลเดียวกับความพยายามของคุณและฉันคิดว่านั่นคือสิ่งที่ไกลที่สุดที่เราจะได้รับ:

struct Foo {
    auto self_() -> decltype(*this) { return *this; }

    using self = decltype(self_());
};

โดยพื้นฐานแล้วสิ่งนี้แสดงให้เห็นว่าขอบเขตที่เราต้องการประกาศ typedef นั้นไม่มีการเข้าถึง (ไม่ว่าจะเป็นทางตรงหรือทางอ้อม) thisและไม่มีวิธีอื่น (คอมไพเลอร์ที่เป็นอิสระ) ในการเข้าถึงประเภทหรือชื่อของคลาส


4
สิ่งนี้จะเป็นไปได้ไหมกับการหักประเภทผลตอบแทนของ C ++ 1y
dyp

4
@dyp เพื่อจุดประสงค์ของคำตอบของฉันที่จะไม่เปลี่ยนแปลงอะไร ข้อผิดพลาดที่นี่ไม่ได้อยู่ในประเภทการส่งคืนต่อท้าย แต่อยู่ในการร้องขอ
Konrad Rudolph

1
@quetzalcoatl: อวัยวะภายในdecltypeเป็นบริบทที่ไม่ได้ประเมินดังนั้นการเรียกใช้ฟังก์ชันสมาชิกจึงไม่ใช่ปัญหา (ซึ่งจะไม่พยายาม)
Lightness Races ในวงโคจร

1
@TomKnapen ลองด้วยเสียงดังลั่นแล้วมันจะล้มเหลว ความจริงที่ GCC ยอมรับนั้นเป็นข้อบกพร่องเท่าที่ฉันทราบ

4
FWIW struct S { int i; typedef decltype(i) Int; };ทำงานได้แม้ว่าiจะเป็นสมาชิกข้อมูลที่ไม่คงที่ ใช้งานdecltypeได้เนื่องจากมีข้อยกเว้นพิเศษที่ชื่อธรรมดาไม่ได้รับการประเมินเป็นนิพจน์ แต่ฉันคิดไม่ออกว่าจะใช้ความเป็นไปได้นี้เพื่อตอบคำถามอย่างไร

21

สิ่งที่ใช้ได้ทั้ง GCC และ clang คือการสร้าง typedef ที่อ้างถึงthisโดยใช้thisในฟังก์ชันtrailing-return-type ของฟังก์ชัน typedef เนื่องจากนี่ไม่ใช่การประกาศฟังก์ชันสมาชิกแบบคงที่การใช้งานthisจึงได้รับการยอมรับ จากนั้นคุณสามารถใช้ typedef selfที่จะกำหนด

#define DEFINE_SELF() \
    typedef auto _self_fn() -> decltype(*this); \
    using self = decltype(((_self_fn*)0)())

struct Foo {
    DEFINE_SELF();
};

struct Bar {
    DEFINE_SELF();
};

น่าเสียดายที่การอ่านมาตรฐานอย่างเข้มงวดบอกว่าแม้จะไม่ถูกต้อง สิ่งที่ส่งเสียงดังคือการตรวจสอบที่thisไม่ได้ใช้ในนิยามของฟังก์ชันสมาชิกแบบคงที่ และที่นี่มันไม่ใช่อย่างแน่นอน GCC ไม่คำนึงว่าthisจะใช้ในประเภทผลตอบแทนต่อท้ายโดยไม่คำนึงถึงประเภทของฟังก์ชันมันจะอนุญาตให้ใช้แม้กระทั่งสำหรับstaticฟังก์ชันสมาชิก อย่างไรก็ตามสิ่งที่มาตรฐานต้องการคือสิ่งที่thisไม่ได้ใช้นอกเหนือจากนิยามของฟังก์ชันสมาชิกที่ไม่คงที่ (หรือตัวเริ่มต้นสมาชิกข้อมูลที่ไม่คงที่) Intel ทำให้ถูกต้องและปฏิเสธสิ่งนี้

ระบุว่า:

  • this อนุญาตเฉพาะในการเริ่มต้นสมาชิกข้อมูลแบบไม่คงที่และฟังก์ชันสมาชิกที่ไม่คงที่ ([expr.prim.general] p5)
  • สมาชิกข้อมูลที่ไม่คงที่ไม่สามารถอนุมานประเภทจากตัวเริ่มต้น ([dcl.spec.auto] p5),
  • ฟังก์ชันสมาชิกที่ไม่คงที่สามารถอ้างถึงได้ด้วยชื่อที่ไม่มีเงื่อนไขในบริบทของการเรียกใช้ฟังก์ชัน ([expr.ref] p4)
  • ฟังก์ชันสมาชิกที่ไม่คงที่สามารถเรียกได้ด้วยชื่อที่ไม่มีเงื่อนไขเท่านั้นแม้ในบริบทที่ไม่ประเมินค่าเมื่อthisสามารถใช้งานได้ ([over.call.func] p3)
  • การอ้างอิงถึงฟังก์ชันสมาชิกแบบไม่คงที่โดยใช้ชื่อที่มีคุณสมบัติหรือการเข้าถึงของสมาชิกจำเป็นต้องมีการอ้างอิงถึงประเภทที่กำหนด

ฉันคิดว่าฉันสามารถสรุปได้ว่าไม่มีวิธีใดเลยที่จะนำไปใช้selfโดยไม่รวมชื่อประเภทไว้ในที่ใดที่หนึ่ง

แก้ไข : มีข้อบกพร่องในการให้เหตุผลก่อนหน้านี้ของฉัน "ฟังก์ชันสมาชิกที่ไม่คงที่สามารถเรียกได้โดยใช้ชื่อที่ไม่มีเงื่อนไขเท่านั้นแม้ในบริบทที่ไม่ได้ประเมินค่าเมื่อสามารถใช้ ([over.call.func] p3)" ไม่ถูกต้อง สิ่งที่พูดจริงคือ

หากคำหลักthis(9.3.2) อยู่ในขอบเขตและความหมายถึงระดับTหรือชั้นมาของแล้วอาร์กิวเมนต์วัตถุโดยนัยคือT (*this)หากคีย์เวิร์ดthisไม่อยู่ในขอบเขตหรืออ้างถึงคลาสอื่นอ็อบเจ็กต์ประเภทที่สร้างขึ้นTจะกลายเป็นอาร์กิวเมนต์อ็อบเจ็กต์โดยนัย หากรายการอาร์กิวเมนต์ถูกเสริมด้วยอ็อบเจ็กต์ที่สร้างขึ้นและความละเอียดโอเวอร์โหลดเลือกหนึ่งในฟังก์ชันสมาชิกที่ไม่คงที่ของTการเรียกใช้รูปแบบไม่ถูกต้อง

ภายในฟังก์ชันสมาชิกแบบคงที่thisอาจไม่ปรากฏขึ้น แต่ยังคงมีอยู่

อย่างไรก็ตามตามความคิดเห็นภายในฟังก์ชันสมาชิกแบบคงที่การแปลงf()เป็น(*this).f()จะไม่ถูกดำเนินการและไม่ได้ดำเนินการดังนั้น [expr.call] p1 จะถูกละเมิด:

[... ] สำหรับการเรียกใช้ฟังก์ชันสมาชิกนิพจน์ postfix จะเป็นการเข้าถึงสมาชิกคลาสโดยนัย (9.3.1, 9.4) หรืออย่างชัดเจน (5.2.5) ซึ่ง [... ]

เนื่องจากจะไม่มีการเข้าถึงสมาชิก ถึงอย่างนั้นก็ไม่ได้ผล


ผมคิดว่า [class.mfct.non ไฟฟ้าสถิต] / 3 บอกว่า_self_fn_1()จะ "เปลี่ยน" (*this)._self_fn_1()ใน ไม่แน่ใจว่าทำผิดกฎหมายหรือไม่
dyp

@dyp มันบอกว่า "ถูกใช้ในสมาชิกของคลาสXในบริบทที่thisสามารถใช้ได้" ดังนั้นฉันไม่คิดว่าจะมีการเปลี่ยนแปลง

1
แต่มันก็ไม่เป็นการเข้าถึงสมาชิกชั้นเรียนโดยปริยายหรือชัดเจน .. ? [expr.call] / 1 "สำหรับการเรียกฟังก์ชันสมาชิกนิพจน์ postfix จะเป็นการเข้าถึงสมาชิกคลาสโดยนัยหรือชัดเจน [... ]"
dyp

(ฉันหมายความว่าจะเกิดอะไรขึ้นเมื่อคุณมีauto _self_fn_1() -> decltype(*this); auto _self_fn_1() const -> decltype(*this);)
dyp

@dyp [expr.call] / 1 เป็นจุดที่ดีฉันจะต้องดูให้ละเอียดขึ้น เกี่ยวกับการconstโอเวอร์โหลดแม้ว่านั่นไม่ใช่ปัญหา 5.1p3 ได้รับโดยเฉพาะการแก้ไขเพื่อนำไปใช้กับฟังก์ชั่นสมาชิกแบบคงที่และบอกประเภทของการthisเป็นFoo*/ Bar*(ไม่const) เพราะไม่มีในประกาศของconst _self_fn_2

17
#define SELF_CHECK( SELF ) void self_check() { static_assert( std::is_same< typename std::decay<decltype(*this)>::type, SELF >::value, "self wrong type" ); }
#define SELF(T) typedef T self; SELF_CHECK(T)

struct Foo {
  SELF(Foo); // works, self is defined as `Foo`
};
struct Bar {
  SELF(Foo); // fails
};

สิ่งนี้ใช้ไม่ได้กับประเภทเทมเพลตเนื่องจากself_checkไม่มีการเรียกดังนั้นจึงstatic_assertไม่ได้รับการประเมิน

เราสามารถแฮ็คบางอย่างเพื่อให้มันใช้งานtemplateได้เช่นกัน แต่มีค่าใช้จ่ายในการรันไทม์เล็กน้อย

#define TESTER_HELPER_TYPE \
template<typename T, std::size_t line> \
struct line_tester_t { \
  line_tester_t() { \
    static_assert( std::is_same< decltype(T::line_tester), line_tester_t<T,line> >::value, "test failed" ); \
    static_assert( std::is_same< decltype(&T::static_test_zzz), T*(*)() >::value, "test 2 failed" ); \
  } \
}

#define SELF_CHECK( SELF ) void self_check() { static_assert( std::is_same< typename std::decay<decltype(*this)>::type, SELF >::value, "self wrong type" ); }

#define SELF(T) typedef T self; SELF_CHECK(T); static T* static_test_zzz() { return nullptr; }; TESTER_HELPER_TYPE; line_tester_t<T,__LINE__> line_tester

สร้างห้องว่างstructขนาด 1 ไบต์ในชั้นเรียนของคุณ หากประเภทของคุณถูกสร้างอินสแตนซ์selfจะทดสอบกับ


ก็ไม่เลวเช่นกัน!
Lightness Races ใน Orbit

@LightnessRacesinOrbit พร้อมtemplateตัวเลือกการสนับสนุนคลาสแล้ว
Yakk - Adam Nevraumont

ฉันกำลังคิดอย่างตรงไปตรงมาเกี่ยวกับเรื่องนี้เมื่อฉันออกจากงานเมื่อวานนี้ คุณเอาชนะฉันไป :) ฉันขอแนะนำให้ประกาศ self_check () เป็นแบบอินไลน์เพื่อหลีกเลี่ยงปัญหาการเชื่อมโยง (สัญลักษณ์เดียวกัน Foo :: self_check () ที่พบในไฟล์ออบเจ็กต์หลายไฟล์)
สุกร

1
@theswine: 9.3 / 2 เป็นดัชนีของวรรคใน C ++ มาตรฐานซึ่งรับประกันได้ว่าฟังก์ชั่นสมาชิกชั้นเรียนที่กำหนดไว้ในร่างของการกำหนดระดับที่inlineมีอยู่แล้วโดยปริยาย นั่นหมายความว่าคุณไม่จำเป็นต้องเขียนinlineเลย ดังนั้นหากคุณเขียนinlineไว้หน้านิยามฟังก์ชันของสมาชิกคลาสทุกอาชีพคุณสามารถหยุดได้เลย;)
Lightness Races ใน Orbit

2
@LightnessRacesinOrbit โอ้ที่จริงฉันเป็น ขอบคุณที่จะช่วยฉันในการพิมพ์ในอนาคต :) ฉันมักจะประหลาดใจกับการที่ฉันไม่รู้เกี่ยวกับ C ++ มากแค่ไหน
สุกร

11

ฉันยังคิดว่ามันเป็นไปไม่ได้นี่เป็นความล้มเหลวอีกครั้ง แต่ความพยายามที่น่าสนใจของ IMHO ซึ่งหลีกเลี่ยงการthisเข้าถึง:

template<typename T>
struct class_t;

template<typename T, typename R>
struct class_t< R (T::*)() > { using type = T; };

struct Foo
{
   void self_f(); using self = typename class_t<decltype(&self_f)>::type;
};

#include <type_traits>

int main()
{
    static_assert( std::is_same< Foo::self, Foo >::value, "" );
}

ซึ่งล้มเหลวเนื่องจาก C ++ ต้องการให้คุณมีคุณสมบัติself_fกับคลาสเมื่อคุณต้องการรับที่อยู่ :(


และปัญหาเดียวกันนี้เกิดขึ้นกับint T::*ตัวแปรตัวชี้ไปยังสมาชิกปกติ และไม่ได้ทำงานอย่างใดอย่างหนึ่งที่เพียงปกติint self_var; typedef decltype(&self_var) self_ptr int*
MSalters

9

ฉันเพิ่งค้นพบว่า*thisได้รับอนุญาตในรั้งหรือเท่ากับการเริ่มต้น อธิบายไว้ใน§ 5.1.1 ( จากแบบร่างการทำงานของ n3337 ):

3 [.. ]ซึ่งแตกต่างจากนิพจน์อ็อบเจ็กต์ในบริบทอื่น ๆ*thisไม่จำเป็นต้องเป็นประเภทที่สมบูรณ์เพื่อจุดประสงค์ในการเข้าถึงสมาชิกคลาส (5.2.5) นอกเนื้อหาฟังก์ชันสมาชิก [.. ]

4 มิฉะนั้นหากผู้ประกาศสมาชิกประกาศสมาชิกข้อมูลที่ไม่คงที่ (9.2) ของคลาส X นิพจน์thisจะเป็นค่าพรีเมี่ยมของประเภท "ตัวชี้ไปที่ X" ภายในวงเล็บปีกกาหรือค่าเริ่มต้นที่เป็นทางเลือก มันจะไม่ปรากฏที่อื่น ๆ ในสมาชิก declarator

5 การแสดงออกthisจะต้องไม่ปรากฏในบริบทอื่นใด [ ตัวอย่าง:

class Outer {
    int a[sizeof(*this)];               // error: not inside a member function
    unsigned int sz = sizeof(*this);    // OK: in brace-or-equal-initializer

    void f() {
        int b[sizeof(*this)];           // OK
        struct Inner {
            int c[sizeof(*this)];       // error: not inside a member function of Inner
        };
    }
};

- ตัวอย่างตอนท้าย ]

ด้วยเหตุนี้รหัสต่อไปนี้:

struct Foo
{
    Foo* test = this;
    using self = decltype(test);

    static void smf()
    {
        self foo;
    }
};

#include <iostream>
#include <type_traits>

int main()
{
    static_assert( std::is_same< Foo::self, Foo* >::value, "" );
}

ผ่านแดเนียลเฟรย์ static_assert

Live example


คุณมีตัวแปรที่ไร้ประโยชน์ที่น่ารำคาญtestแม้ว่า
MM

@ แมททรู แต่ยังพบว่าน่าสนใจ

1
สิ่งนี้อาจได้ผลโดยไม่ต้อง= thisใช่ไหม? และทำไมไม่เพียงusing self = Foo*;
user362515

1
เราไม่ได้อะไรมาที่นี่แน่ ๆ เพราะต้องประกาศว่าtestเป็นคนประเภทอืมFoo *!
Paul Sanders

4

เว้นแต่ประเภทจะต้องมีประเภทสมาชิกของชั้นเรียนการปิดล้อมที่คุณสามารถใช้ทดแทนการใช้งานด้วยself decltype(*this)หากคุณใช้มันในหลาย ๆ ที่ในโค้ดของคุณคุณสามารถกำหนดมาโครได้SELFดังนี้:

#define SELF decltype(*this)

2
และคุณไม่สามารถใช้สิ่งนั้นนอกชั้นเรียนหรือในชั้นเรียนที่ซ้อนกันได้
Drax

1
@Drax: มันไม่ควรมีอยู่นอกชั้นเรียน
Ben Voigt

@BenVoigt แต่มันถูกกำหนดให้พร้อมใช้งานในคลาสที่ซ้อนกันซึ่ง IMO เป็นกรณีการใช้งานที่น่าสนใจที่สุด
Drax

1
ฉันไม่คิดอย่างนั้น ไม่ควรselfอ้างถึงคลาสปิดล้อมทันทีไม่ใช่คลาสนอก? แต่ฉันไม่รู้จัก php ที่ดี
Ben Voigt

1
@LightnessRacesinOrbit: ฉันเดาว่ารหัสและข้อผิดพลาดควรจะบอกว่า "PHP ไม่มีประเภทซ้อนกัน"?
Ben Voigt

1

ระบุเวอร์ชันของฉัน สิ่งที่ดีที่สุดคือการใช้งานเหมือนกับคลาสพื้นเมือง อย่างไรก็ตามมันใช้ไม่ได้กับคลาสเทมเพลต

template<class T> class Self;

#define CLASS(Name) \
class Name##_; \
typedef Self<Name##_> Name; \
template<> class Self<Name##_>

CLASS(A)
{
    int i;
    Self* clone() const { return new Self(*this); }
};

CLASS(B) : public A
{
    float f;
    Self* clone() const { return new Self(*this); }
};

1

จากคำตอบโดย hvd ฉันพบว่าสิ่งเดียวที่ขาดหายไปคือการลบการอ้างอิงนั่นคือสาเหตุที่การตรวจสอบ std :: is_same ล้มเหลว (b / c ประเภทผลลัพธ์เป็นการอ้างอิงถึงประเภท) ตอนนี้มาโครที่ไม่มีพารามิเตอร์นี้สามารถทำงานทั้งหมดได้ ตัวอย่างการทำงานด้านล่าง (ฉันใช้ GCC 8.1.1)

#define DEFINE_SELF \
    typedef auto _self_fn() -> std::remove_reference<decltype(*this)>::type; \
    using self = decltype(((_self_fn*)0)())

class A {
    public:
    DEFINE_SELF;
};

int main()
{
    if (std::is_same_v<A::self, A>)
        std::cout << "is A";
}

มันไม่ได้รวบรวมบนคอมไพเลอร์อื่นที่ไม่ใช่ GCC
zedu

0

ฉันจะทำซ้ำวิธีแก้ปัญหาที่ชัดเจนของ "ต้องทำเอง" นี่คือโค้ดเวอร์ชัน C ++ 11 แบบรวบรัดซึ่งใช้ได้กับทั้งคลาสธรรมดาและเทมเพลตคลาส:

#define DECLARE_SELF(Type) \
    typedef Type TySelf; /**< @brief type of this class */ \
    /** checks the consistency of TySelf type (calling it has no effect) */ \
    void self_check() \
    { \
        static_assert(std::is_same<decltype(*((TySelf*)(0))), \
            decltype(*this)>::value, "TySelf is not what it should be"); \
    } \
    enum { static_self_check_token = __LINE__ }; \
    static_assert(int(static_self_check_token) == \
        int(TySelf::static_self_check_token), \
        "TySelf is not what it should be")

คุณสามารถดูได้ในการกระทำที่ideone ต้นกำเนิดที่นำไปสู่ผลลัพธ์นี้อยู่ด้านล่าง:

#define DECLARE_SELF(Type) typedef Type _TySelf; /**< @brief type of this class */

struct XYZ {
    DECLARE_SELF(XYZ)
};

สิ่งนี้มีปัญหาชัดเจนในการคัดลอกโค้ดไปยังคลาสอื่นและลืมเปลี่ยน XYZ เช่นที่นี่:

struct ABC {
    DECLARE_SELF(XYZ) // !!
};

แนวทางแรกของฉันไม่ได้เป็นต้นฉบับมากนัก - การสร้างฟังก์ชันเช่นนี้:

/**
 *  @brief namespace for checking the _TySelf type consistency
 */
namespace __self {

/**
 *  @brief compile-time assertion (_TySelf must be declared the same as the type of class)
 *
 *  @tparam _TySelf is reported self type
 *  @tparam _TyDecltypeThis is type of <tt>*this</tt>
 */
template <class _TySelf, class _TyDecltypeThis>
class CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE;

/**
 *  @brief compile-time assertion (specialization for assertion passing)
 *  @tparam _TySelf is reported self type (same as type of <tt>*this</tt>)
 */
template <class _TySelf>
class CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE<_TySelf, _TySelf> {};

/**
 *  @brief static assertion helper type
 *  @tparam n_size is size of object being used as assertion message
 *      (if it's a incomplete type, compiler will display object name in error output)
 */
template <const size_t n_size>
class CStaticAssert {};

/**
 *  @brief helper function for self-check, this is used to derive type of this
 *      in absence of <tt>decltype()</tt> in older versions of C++
 *
 *  @tparam _TyA is reported self type
 *  @tparam _TyB is type of <tt>*this</tt>
 */
template <class _TyA, class _TyB>
inline void __self_check_helper(_TyB *UNUSED(p_this))
{
    typedef CStaticAssert<sizeof(CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE<_TyA, _TyB>)> _TyAssert;
    // make sure that the type reported as self and type of *this is the same
}

/**
 *  @def __SELF_CHECK
 *  @brief declares the body of __self_check() function
 */
#define __SELF_CHECK \
    /** checks the consistency of _TySelf type (calling it has no effect) */ \
    inline void __self_check() \
    { \
        __self::__self_check_helper<_TySelf>(this); \
    }

/**
 *  @def DECLARE_SELF
 *  @brief declares _TySelf type and adds code to make sure that it is indeed a correct one
 *  @param[in] Type is type of the enclosing class
 */
#define DECLARE_SELF(Type) \
    typedef Type _TySelf; /**< @brief type of this class */ \
    __SELF_CHECK

} // ~self

มันค่อนข้างยาว แต่โปรดอดทนกับฉันที่นี่ สิ่งนี้มีข้อดีของการทำงานใน C ++ 03 โดยไม่ต้องdecltypeใช้__self_check_helperฟังก์ชันนี้เพื่ออนุมานประเภทของthis. นอกจากนี้ยังไม่มีstatic_assertแต่ใช้sizeof()เคล็ดลับแทน คุณสามารถทำให้ C ++ 0x สั้นลงได้มาก ตอนนี้จะใช้ไม่ได้กับเทมเพลต นอกจากนี้ยังมีปัญหาเล็กน้อยเกี่ยวกับมาโครที่ไม่คาดหวังอัฒภาคในตอนท้ายหากรวบรวมด้วยความอวดดีมันจะบ่นเกี่ยวกับอัฒภาคที่ไม่จำเป็นเพิ่มเติม (หรือคุณจะเหลือมาโครที่ดูแปลก ๆ ที่ไม่ได้ลงท้ายด้วยอัฒภาคในเนื้อความXYZและABC).

การตรวจสอบสิ่งTypeที่ส่งผ่านไปDECLARE_SELFไม่ใช่ทางเลือกเนื่องจากจะตรวจสอบเฉพาะXYZคลาส (ซึ่งก็ใช้ได้) โดยไม่สนใจABC(ซึ่งมีข้อผิดพลาด) แล้วมันก็โดนฉัน โซลูชันศูนย์ต้นทุนพื้นที่จัดเก็บที่ไม่มีค่าใช้จ่ายเพิ่มเติมซึ่งทำงานร่วมกับเทมเพลต:

namespace __self {

/**
 *  @brief compile-time assertion (_TySelf must be declared the same as the type of class)
 *  @tparam b_check is the asserted value
 */
template <bool b_check>
class CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE2;

/**
 *  @brief compile-time assertion (specialization for assertion passing)
 */
template <>
class CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE2<true> {};

/**
 *  @def DECLARE_SELF
 *  @brief declares _TySelf type and adds code to make sure that it is indeed a correct one
 *  @param[in] Type is type of the enclosing class
 */
#define DECLARE_SELF(Type) \
    typedef Type _TySelf; /**< @brief type of this class */ \
    __SELF_CHECK \
    enum { __static_self_check_token = __LINE__ }; \
    typedef __self::CStaticAssert<sizeof(CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE2<int(__static_self_check_token) == int(_TySelf::__static_self_check_token)>)> __static_self_check

} // ~__self 

สิ่งนี้ทำให้การยืนยันแบบคงที่สำหรับค่า enum ที่ไม่ซ้ำกัน (หรืออย่างน้อยก็ไม่ซ้ำกันในกรณีที่คุณไม่ได้เขียนโค้ดทั้งหมดของคุณในบรรทัดเดียว) ไม่มีการใช้กลอุบายเปรียบเทียบประเภทใด ๆ และทำงานเป็นคำยืนยันแบบคงที่แม้ในเทมเพลต . และเป็นโบนัส - ตอนนี้จำเป็นต้องใช้อัฒภาคสุดท้าย :)

ฉันขอขอบคุณ Yakk ที่ทำให้ฉันมีแรงบันดาลใจที่ดี ฉันจะไม่เขียนสิ่งนี้โดยไม่ได้เห็นคำตอบของเขาก่อน

ทดสอบด้วย VS 2008 และ g ++ 4.6.3 อันที่จริงด้วยXYZและABCตัวอย่างมันบ่น:

ipolok@ivs:~$ g++ self.cpp -c -o self.o
self.cpp:91:5: error: invalid application of âsizeofâ to incomplete type â__self::CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE2<false>â
self.cpp:91:5: error: template argument 1 is invalid
self.cpp: In function âvoid __self::__self_check_helper(_TyB*) [with _TyA = XYZ, _TyB = ABC]â:
self.cpp:91:5:   instantiated from here
self.cpp:58:87: error: invalid application of âsizeofâ to incomplete type â__self::CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE<XYZ, ABC

ตอนนี้ถ้าเราสร้าง ABC เป็นเทมเพลต:

template <class X>
struct ABC {
    DECLARE_SELF(XYZ); // line 92
};

int main(int argc, char **argv)
{
    ABC<int> abc;
    return 0;
}

เราจะได้รับ:

ipolok@ivs:~$ g++ self.cpp -c -o self.o
self.cpp: In instantiation of âABC<int>â:
self.cpp:97:18:   instantiated from here
self.cpp:92:9: error: invalid application of âsizeofâ to incomplete type â__self::CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE2<false>â

เฉพาะการตรวจสอบหมายเลขบรรทัดเท่านั้นที่ทริกเกอร์เนื่องจากการตรวจสอบฟังก์ชันไม่ได้รวบรวม (ตามที่คาดไว้)

ด้วย C ++ 0x (และไม่มีขีดล่างชั่วร้าย) คุณจะต้องมีเพียง:

namespace self_util {

/**
 *  @brief compile-time assertion (tokens in class and TySelf must match)
 *  @tparam b_check is the asserted value
 */
template <bool b_check>
class SELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE;

/**
 *  @brief compile-time assertion (specialization for assertion passing)
 */
template <>
class SELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE<true> {};

/**
 *  @brief static assertion helper type
 *  @tparam n_size is size of object being used as assertion message
 *      (if it's a incomplete type, compiler will display object name in error output)
 */
template <const size_t n_size>
class CStaticAssert {};

#define SELF_CHECK \
    /** checks the consistency of TySelf type (calling it has no effect) */ \
    void self_check() \
    { \
        static_assert(std::is_same<TySelf, decltype(*this)>::value, "TySelf is not what it should be"); \
    }

#define DECLARE_SELF(Type) \
    typedef Type TySelf; /**< @brief type of this class */ \
    SELF_CHECK \
    enum { static_self_check_token = __LINE__ }; \
    typedef self_util::CStaticAssert<sizeof(SELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE<int(static_self_check_token) == int(TySelf::static_self_check_token)>)> static_self_check

} // ~self_util

ฉันเชื่อว่าบิต CStaticAssert ยังคงต้องใช้อย่างน่าเสียดายเนื่องจากสร้างประเภทซึ่งเป็น typedef-ed ในเนื้อหาเทมเพลต (ฉันคิดว่าไม่สามารถทำได้ด้วยstatic_assert) ข้อดีของแนวทางนี้ยังคงเป็นต้นทุนที่เป็นศูนย์


คุณกำลังนำกลับมาใช้static_assertที่นี่เป็นหลักใช่ไหม นอกจากนี้รหัสที่สมบูรณ์ของคุณไม่ถูกต้องเนื่องจากคุณใช้ตัวระบุที่ผิดกฎหมาย (สงวนไว้)
Konrad Rudolph

@KonradRudolph ใช่เป็นอย่างนั้นจริงๆ ฉันไม่มี C ++ 0x ในขณะนั้นดังนั้นฉันจึงติดตั้ง static_assert ใหม่เพื่อให้คำตอบที่สมบูรณ์ ผมว่าในคำตอบ มันไม่ถูกต้อง? คุณสามารถชี้ให้เห็นวิธี? เรียบเรียงได้ดีตอนนี้ฉันกำลังใช้อยู่
สุกร

1
ตัวระบุไม่ถูกต้องเนื่องจาก C ++ สงวนทุกอย่างด้วยขีดล่างนำหน้าตามด้วยอักษรตัวพิมพ์ใหญ่เช่นเดียวกับขีดล่างสองตัวในขอบเขตส่วนกลางสำหรับคอมไพเลอร์ ต้องไม่ใช้รหัสผู้ใช้ แต่ไม่ใช่ทุกคอมไพเลอร์ที่จะตั้งค่าสถานะเป็นข้อผิดพลาด
Konrad Rudolph

@KonradRudolph ฉันเห็นฉันไม่รู้ว่า ฉันมีรหัสจำนวนมากที่ใช้สิ่งนั้นไม่เคยมีปัญหากับมันบน Linux / Mac / Windows แต่ฉันคิดว่ามันเป็นเรื่องดีที่จะรู้
สุกร

0

ฉันไม่รู้ทั้งหมดเกี่ยวกับเทมเพลตที่แปลกประหลาดเหล่านี้แล้วสิ่งที่ง่ายสุด ๆ :

#define DECLARE_TYPEOF_THIS typedef CLASSNAME typeof_this
#define ANNOTATED_CLASSNAME(DUMMY) CLASSNAME

#define CLASSNAME X
class ANNOTATED_CLASSNAME (X)
{
public:
    DECLARE_TYPEOF_THIS;
    CLASSNAME () { moi = this; }
    ~CLASSNAME () { }
    typeof_this *moi;
    // ...
};    
#undef CLASSNAME

#define CLASSNAME Y
class ANNOTATED_CLASSNAME (Y)
{
    // ...
};
#undef CLASSNAME

งานเสร็จแล้วเว้นแต่คุณจะไม่สามารถยืนมาโครได้สองสามตัว คุณยังสามารถใช้CLASSNAMEเพื่อประกาศตัวสร้างของคุณ (และแน่นอนว่าผู้ทำลาย)

การสาธิตสด


1
มันมีผลอย่างชัดเจนต่อวิธีที่คลาสสามารถ / ต้องใช้
Lightness Races in Orbit

@LightnessRacesinOrbit ยังไง? ไม่เห็นเป็นไรเลย ฉันได้ลบประโยคสุดท้ายของโพสต์เดิมของฉันในการไตร่ตรอง สิ่งที่ฉันมีในตอนแรกอาจทำให้คุณคิดอย่างนี้
Paul Sanders
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.