Tôi có thể triển khai kiểu thành viên `self` tự trị trong C ++ không?


101

C ++ thiếu từ khóa tương đương của PHP, selftừ khóa này đánh giá loại của lớp bao quanh.

Thật dễ dàng để làm giả nó trên cơ sở từng lớp:

struct Foo
{
   typedef Foo self;
};

nhưng tôi đã phải viết Foolại. Có lẽ một ngày nào đó tôi sẽ làm sai điều này và gây ra một lỗi im lặng.

Tôi có thể sử dụng một số kết hợp của decltypevà những người bạn để làm cho công việc này "tự chủ" không? Tôi đã thử những cách sau nhưng thiskhông hợp lệ ở nơi đó:

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

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

(Tôi sẽ không lo lắng về điều tương tự static, tương tự như vậy nhưng với ràng buộc muộn.)


9
this_tcó lẽ sẽ phù hợp hơn với cách đặt tên C ++ thông thường.
Bartek Banachewicz

3
@BartekBanachewicz: hoặc this_type
PlasmaHH

10
@Praetorian, tôi không thể nhớ đó có phải là một đề xuất hay không, nhưng ai đó đã đề xuất auto()~auto()cho ctors / dtors. Thú vị để nói rằng ít nhất. Nếu được sử dụng cho mục đích đó, có lẽ typedef auto self;, nhưng điều đó có vẻ hơi sơ sài đối với tôi.
chris

11
Thành thật mà nói, nếu tôi định đề xuất cú pháp để làm cho điều này có thể thực hiện được, nó có thể sẽ là decltype(class), có thể với một decltype(struct)tương đương. Điều đó rõ ràng hơn nhiều so với chỉ autotrong một ngữ cảnh cụ thể và tôi không thấy bất kỳ vấn đề nào với việc nó phù hợp với ngôn ngữ dựa trên decltype(auto).
chris

11
Vì bạn muốn tránh sai sót, bạn có thể thiết lập một chức năng thành viên dummy với static_assert, giống như void _check() { static_assert(std::is_same<self&, decltype(*this)>::value, "Correct your self type"); }không làm việc với các lớp mẫu, mặc dù ...
milleniumbug

Câu trả lời:


38

Đây là cách bạn có thể làm điều đó mà không cần lặp lại kiểu 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;
    }
};

Nếu bạn muốn lấy từ Foođó, bạn nên sử dụng macro WITH_SELF_DERIVEDtheo cách sau:

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

Bạn thậm chí có thể thực hiện đa kế thừa với bao nhiêu lớp cơ sở tùy thích (nhờ các mẫu đa dạng và macro đa dạng):

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

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

Tôi đã xác minh điều này hoạt động trên gcc 4.8 và clang 3.4.


18
Tôi đoán câu trả lời là "không, nhưng Ralph có thể!" ;)
Lightness Races ở Orbit

3
Làm thế nào điều này theo bất kỳ cách nào tốt hơn so với việc chỉ cần đặt typedef vào đó? Và chúa ơi, tại sao bạn lại cần đến typedef? Tại sao?
Miles Rout

7
@MilesRout Đây là câu hỏi về câu hỏi, không phải câu trả lời. Trong nhiều trường hợp trong phát triển phần mềm (và đặc biệt là bảo trì), việc tránh dư thừa trong mã là rất hữu ích, để việc thay đổi thứ gì đó ở một nơi không yêu cầu bạn phải thay đổi mã ở nơi khác. Đó là toàn bộ điểm của autodecltypehoặc trong trường hợp này self.
Ralph Tandetzky

1
template<typename T>class Self{protected: typedef T self;}; class WITH_SELF(Foo) : public Bar, private Baz {};sẽ đơn giản hơn và sẽ cho phép kiểm soát chính xác hơn đối với việc kế thừa - bất kỳ lý do nào chống lại?
Aconcagua

@mmmmmmmm, nếu bạn chưa học cách cảm nhận sâu sắc nguyên tắc "Đừng lặp lại chính mình", rất có thể bạn chưa viết mã đầy đủ / nghiêm túc. "Sự lộn xộn" này (thực sự khác xa nó) là một cách sửa chữa khá thanh lịch trong bối cảnh nói về một đặc điểm ngôn ngữ không lịch sự (hoặc viết sai, thậm chí thiếu hụt bởi một số biện pháp nghiêm ngặt).
Sz.

38

Một giải pháp khả thi (vì bạn vẫn phải viết loại một lần):

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

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

Đối với một phiên bản an toàn hơn, chúng tôi có thể đảm bảo rằng nó Tthực sự bắt nguồn từ Self<T>:

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

Lưu ý rằng static_assertbên trong một hàm thành viên có lẽ là cách duy nhất để kiểm tra, vì các kiểu được truyền vào std::is_base_ofphải hoàn chỉnh.


4
Không cần typenametrong typedef. Và vì điều này không làm giảm số lượng dư thừa nên tôi không nghĩ đó là một giải pháp thay thế khả thi.
Konrad Rudolph

Nó có cùng một vấn đề về Footên lặp lại .
Bartek Banachewicz

6
phần nhỉnh hơn so với phương pháp ban đầu, tuy nhiên, kể từ khi sự lặp lại là rất gần nhau. Không phải là một giải pháp cho câu hỏi, nhưng +1 cho một nỗ lực xứng đáng đối với giải pháp tình huống tốt nhất.
Các cuộc đua ánh sáng trong quỹ đạo vào

4
Tôi đã sử dụng giải pháp đó một vài lần, và nó có một điều XẤU: khi sau này bắt nguồn từ Foo, bạn phải: (1) truyền chữ T trở lên cho hậu duệ lá, hoặc (2) nhớ kế thừa từ SelfT nhiều lần , hoặc (3) chấp nhận rằng tất cả trẻ em đều là Cơ sở .. có thể sử dụng được, nhưng không tiện lợi.
quetzalcoatl

@quetzalcoatl: Vì tôi đang cố gắng tái tạo selfhơn staticlà nên không có vấn đề gì.
Các cuộc đua ánh sáng trong quỹ đạo

33

Bạn có thể sử dụng macro thay vì khai báo lớp thông thường, điều đó sẽ làm điều đó cho bạn.

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

Và sau đó sử dụng như

CLASS_WITH_SELF(Foo) 
};

#define END_CLASS }; có thể sẽ giúp dễ đọc.


Bạn cũng có thể lấy @ Paranaix's Selfvà sử dụng nó (nó bắt đầu trở nên thực sự khó hiểu)

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

class WITH_SELF(Foo) {
};

18
EWWWW END_CLASS. Điều đó hoàn toàn không cần thiết.
Puppy

31
@DeadMG Tôi nghĩ một số người có thể thích tính nhất quán hơn; xét cho cùng, việc sử dụng macro đầu tiên không kết thúc {, do đó, }hiện tượng "treo", điều mà các nhà soạn thảo văn bản có thể cũng sẽ không thích.
Bartek Banachewicz

6
Ý tưởng hay nhưng mặc dù về cơ bản tôi không phản đối macro, tôi chỉ chấp nhận việc sử dụng nó ở đây nếu nó bắt chước phạm vi C ++, tức là nếu nó có thể sử dụng được CLASS_WITH_SELF(foo) { … };- và tôi nghĩ điều đó là không thể đạt được.
Konrad Rudolph

2
@KonradRudolph Tôi cũng đã thêm một cách để làm điều đó. Không phải là tôi thích nó, chỉ vì lợi ích của sự hoàn chỉnh
Bartek Banachewicz

1
Tuy nhiên, có một số vấn đề với cách tiếp cận đó. Đầu tiên là không cho phép bạn dễ dàng kế thừa lớp (trừ khi bạn sử dụng (các) tham số macro khác), và thứ hai có tất cả các vấn đề về kế thừa từSelf.
Bartek Banachewicz

31

Tôi không có bằng chứng xác thực nhưng tôi nghĩ điều đó là không thể. Điều sau không thành công - vì lý do tương tự như nỗ lực của bạn - và tôi nghĩ đó là điều xa nhất mà chúng tôi có thể nhận được:

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

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

Về cơ bản, điều này chứng tỏ rằng phạm vi mà chúng ta muốn khai báo typedef của mình chỉ đơn giản là không có quyền truy cập (có thể là trực tiếp hoặc gián tiếp) thisvà không có cách nào khác (độc lập với trình biên dịch) để truy cập vào kiểu hoặc tên của lớp.


4
Điều này có thể xảy ra với loại trừ kiểu trả về của C ++ 1y không?
dyp

4
@dyp Với mục đích câu trả lời của tôi sẽ không thay đổi bất cứ điều gì. Lỗi ở đây không phải ở kiểu trả về theo sau mà là ở lời gọi.
Konrad Rudolph

1
@quetzalcoatl: Các bộ phận bên trong decltypelà một bối cảnh unevaluated, vì vậy cách gọi các hàm thành viên không phải là vấn đề (mà sẽ không được cố gắng)
Lightness Races ở Orbit

1
@TomKnapen Hãy thử nó với tiếng kêu, và nó sẽ thất bại. Thực tế là nó được GCC chấp nhận là một lỗi, theo như tôi biết.

4
FWIW, struct S { int i; typedef decltype(i) Int; };hoạt động ngay cả khi ilà một thành viên dữ liệu không tĩnh. Nó hoạt động vì decltypecó một ngoại lệ đặc biệt trong đó một tên đơn giản không được đánh giá là một biểu thức. Nhưng tôi không thể nghĩ ra cách nào để sử dụng khả năng này để trả lời câu hỏi.

21

Những gì hoạt động trong cả GCC và clang là tạo một typedef tham chiếu đến thisbằng cách sử dụng thiskiểu dấu-trả lại của một typedef hàm. Vì đây không phải là khai báo của một hàm thành viên tĩnh nên việc sử dụng thisđược chấp nhận. Sau đó, bạn có thể sử dụng typedef đó để xác định self.

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

struct Foo {
    DEFINE_SELF();
};

struct Bar {
    DEFINE_SELF();
};

Thật không may, việc đọc nghiêm ngặt tiêu chuẩn nói rằng ngay cả điều này cũng không hợp lệ. Những gì clang làm là kiểm tra thiskhông được sử dụng trong định nghĩa của một hàm thành viên tĩnh. Và đây, nó thực sự không phải. GCC không bận tâm nếu thisđược sử dụng trong kiểu trả về sau bất kể loại hàm nào, nó cho phép nó ngay cả đối với các statichàm thành viên. Tuy nhiên, những gì tiêu chuẩn thực sự yêu cầu làthis không được sử dụng bên ngoài định nghĩa của hàm thành viên không tĩnh (hoặc bộ khởi tạo thành viên dữ liệu không tĩnh). Intel hiểu đúng và bác bỏ điều này.

Cho rằng:

  • this chỉ được phép trong bộ khởi tạo thành viên dữ liệu không tĩnh và hàm thành viên không tĩnh ([expr.prim.general] p5),
  • các thành viên dữ liệu không tĩnh không thể có kiểu của chúng được suy ra từ trình khởi tạo ([dcl.spec.auto] p5),
  • các hàm thành viên không tĩnh chỉ có thể được tham chiếu bằng một tên không đủ tiêu chuẩn trong ngữ cảnh của một lệnh gọi hàm ([expr.ref] p4)
  • các hàm thành viên không tĩnh chỉ có thể được gọi bằng tên không đủ tiêu chuẩn, ngay cả trong các ngữ cảnh không được đánh giá, khi thiscó thể được sử dụng ([over.call.func] p3),
  • tham chiếu đến một hàm thành viên không tĩnh bằng tên đủ điều kiện hoặc quyền truy cập thành viên yêu cầu tham chiếu đến loại được định nghĩa

Tôi nghĩ rằng tôi có thể kết luận rằng không có cách nào để thực hiện selfmà không bao gồm một cách nào đó, ở đâu đó, tên loại.

Chỉnh sửa : Có một sai sót trong lập luận trước đó của tôi. "các hàm thành viên không tĩnh chỉ có thể được gọi bằng tên không đủ tiêu chuẩn, ngay cả trong các ngữ cảnh không được đánh giá, khi điều này có thể được sử dụng ([over.call.func] p3)," là không chính xác. Những gì nó thực sự nói là

Nếu từ khóa this(9.3.2) nằm trong phạm vi và tham chiếu đến lớp Thoặc một lớp dẫn xuất của T, thì đối số đối tượng ngụ ý là (*this). Nếu từ khóa thiskhông nằm trong phạm vi hoặc tham chiếu đến một lớp khác, thì một đối tượng kiểu contrived Tsẽ trở thành đối số đối tượng ngụ ý. Nếu danh sách đối số được tăng cường bởi một đối tượng contrived và độ phân giải quá tải chọn một trong các hàm thành viên không tĩnh của T, thì lệnh gọi không được định hình.

Bên trong một hàm thành viên tĩnh, thiscó thể không xuất hiện, nhưng nó vẫn tồn tại.

Tuy nhiên, theo các nhận xét, bên trong một hàm thành viên tĩnh, việc chuyển đổi f()thành (*this).f()sẽ không được thực hiện và nó không được thực hiện, thì [expr.call] p1 bị vi phạm:

[...] Đối với lời gọi hàm thành viên, biểu thức hậu tố phải là quyền truy cập thành viên lớp ngầm (9.3.1, 9.4) hoặc rõ ràng (5.2.5) có [...]

vì sẽ không có quyền truy cập thành viên. Vì vậy, ngay cả điều đó sẽ không hoạt động.


Tôi nghĩ rằng [class.mfct.non-static] / 3 nói rằng nó _self_fn_1()được "chuyển đổi" thành (*this)._self_fn_1(). Tuy nhiên, không chắc liệu điều đó có làm cho nó bất hợp pháp hay không.
dyp

@dyp Nó nói rằng "được sử dụng trong một thành viên của lớp Xtrong ngữ cảnh thiscó thể được sử dụng", vì vậy tôi không nghĩ rằng chuyển đổi đó được thực hiện.

1
Nhưng sau đó nó không phải là quyền truy cập thành viên lớp ngầm hay rõ ràng ..? [expr.call] / 1 "Đối với lời gọi hàm thành viên, biểu thức hậu tố sẽ là quyền truy cập thành viên lớp ẩn hoặc rõ ràng [...]"
dyp

(Ý tôi là, điều gì sẽ xảy ra khi bạn có auto _self_fn_1() -> decltype(*this); auto _self_fn_1() const -> decltype(*this);?)
dyp

@dyp [expr.call] / 1 là một điểm tốt, tôi sẽ phải xem xét kỹ hơn. Về constquá tải, mặc dù: đó không phải là một vấn đề. 5.1p3 đã được sửa đổi một cách cụ thể để cũng áp dụng cho các hàm thành viên tĩnh và cho biết kiểu thisFoo*/ Bar*(không có const), vì không có consttrong khai báo của _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
};

điều này không hoạt động trên các loại mẫu, vì self_checkkhông được gọi, do đó, static_assertkhông được đánh giá.

Chúng tôi có thể thực hiện một số hack để làm cho nó hoạt động templatetốt, nhưng nó có một chi phí thời gian chạy nhỏ.

#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

một trống structcó kích thước 1 byte được tạo trong lớp của bạn. Nếu loại của bạn được khởi tạo, selfđược kiểm tra chống lại.


Điều đó cũng không tệ!
Lightness Races in Orbit

@LightnessRacesinOrbit hiện có templatecác tùy chọn hỗ trợ lớp học.
Yakk - Adam Nevraumont

Tôi đã suy nghĩ chính xác về điều này khi tôi đang đi làm ngày hôm qua. Bạn đánh bại tôi vào nó :). Tôi khuyên bạn nên khai báo self_check () dưới dạng nội tuyến, để tránh các vấn đề liên kết (cùng ký hiệu Foo :: self_check () được tìm thấy trong nhiều tệp đối tượng).
the swine

1
@theswine: 9.3 / 2 là chỉ mục của một đoạn trong tiêu chuẩn C ++, đảm bảo rằng các hàm thành viên lớp được xác định trong phần thân của định nghĩa lớp đã, ngầm hiểu rồi inline. Điều đó có nghĩa là bạn không cần phải viết inlinegì cả. Vì vậy, nếu bạn đã viết inlinetrước mọi định nghĩa hàm thành viên lớp như vậy cho toàn bộ sự nghiệp của mình, bạn có thể dừng lại ngay bây giờ;)
Lightness Races trong Orbit

2
@LightnessRacesinOrbit Ồ, thực ra là tôi. Cảm ơn bạn, điều đó sẽ giúp tôi tiết kiệm một số lần đánh máy trong tương lai :). Tôi luôn ngạc nhiên bởi tôi không biết nhiều về C ++.
con lợn

11

Tôi cũng nghĩ điều đó là không thể, đây là một nỗ lực không thành công nhưng IMHO thú vị đã tránh được this-access:

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, "" );
}

không thành công vì C ++ yêu cầu bạn đủ điều kiện self_fvới lớp khi bạn muốn lấy địa chỉ của nó :(


Và vấn đề tương tự cũng xảy ra với một int T::*con trỏ thông thường đến biến thành viên. Và int self_var; typedef decltype(&self_var) self_ptrcũng không hiệu quả, đó chỉ là chuyện thường ngày int*.
MSalters

9

Gần đây tôi đã phát hiện ra điều đó *thisđược phép trong bộ khởi tạo dấu ngoặc nhọn hoặc dấu bằng . Được mô tả trong § 5.1.1 ( từ bản nháp làm việc n3337 ):

3 [..] Không giống như biểu thức đối tượng trong các ngữ cảnh khác, *thiskhông bắt buộc phải có kiểu hoàn chỉnh cho mục đích truy cập thành viên lớp (5.2.5) bên ngoài thân hàm thành viên. [..]

4 Ngược lại, nếu một bộ khai báo thành viên khai báo một phần tử dữ liệu không tĩnh (9.2) của một lớp X, thì biểu thức thislà một giá trị pr của kiểu “con trỏ tới X” trong bộ khởi tạo dấu ngoặc nhọn-hoặc-bằng tùy chọn . Nó sẽ không xuất hiện ở nơi khác trong trình khai báo thành viên .

5 Biểu thức thissẽ không xuất hiện trong bất kỳ ngữ cảnh nào khác. [ Ví dụ:

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
        };
    }
};

- cuối ví dụ ]

Với ý nghĩ đó, đoạn mã sau:

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, "" );
}

vượt qua Daniel Frey's static_assert .

Live example


Bạn có một biến vô dụng gây phiền nhiễu testmặc dù
MM

@Matt Đúng, nhưng tôi vẫn thấy nó thú vị.

1
Điều này có thể đã hoạt động mà không có = this, phải không? Và tại sao không chỉusing self = Foo*;
user362515

1
Chắc chắn chúng tôi không đạt được gì ở đây, bởi vì chúng tôi phải tuyên bố testthuộc loại, ừm Foo *,!
Paul Sanders

4

Trừ khi loại cần phải là loại thành viên của lớp bao quanh, bạn có thể thay thế việc sử dụng selfbằng decltype(*this). Nếu bạn sử dụng nó ở nhiều nơi trong mã của mình, bạn có thể xác định một macro SELFnhư sau:

#define SELF decltype(*this)

2
Và bạn không thể sử dụng bên ngoài của lớp, hoặc trong các lớp lồng nhau
Drax

1
@Drax: Nó không được cho là có sẵn bên ngoài lớp học.
Ben Voigt

@BenVoigt Nhưng nó được cho là có sẵn trong các lớp lồng nhau, IMO là trường hợp sử dụng thú vị nhất.
Drax,

1
Tôi không nghĩ vậy. Không nên selftham chiếu đến lớp bao quanh ngay lập tức và không phải lớp ngoài? Nhưng tôi không biết rõ về php đó.
Ben Voigt

1
@LightnessRacesinOrbit: Tôi đoán rằng mã và lỗi được cho là "PHP không có các kiểu lồng nhau"?
Ben Voigt

1

Cung cấp phiên bản của tôi. Điều tốt nhất là việc sử dụng nó giống như lớp bản địa. Tuy nhiên, nó không hoạt động đối với các lớp mẫu.

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

Dựa trên câu trả lời của hvd, tôi thấy rằng điều duy nhất bị thiếu là xóa tham chiếu, đó là lý do tại sao kiểm tra std :: is_same không thành công (b / c kiểu kết quả thực sự là tham chiếu đến kiểu). Bây giờ macro ít tham số này có thể thực hiện tất cả công việc. Ví dụ làm việc bên dưới (Tôi sử dụng 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";
}

Nó không biên dịch trên các trình biên dịch khác ngoài GCC.
zedu

0

Tôi sẽ nhắc lại giải pháp hiển nhiên là "phải tự mình làm". Đây là phiên bản C ++ 11 ngắn gọn của mã, hoạt động với cả các lớp đơn giản và các mẫu lớp:

#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")

Bạn có thể thấy nó hoạt động tại Ideone . Nguồn gốc, dẫn đến kết quả này như sau:

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

struct XYZ {
    DECLARE_SELF(XYZ)
};

Điều này có vấn đề rõ ràng là sao chép dán mã vào một lớp khác và quên thay đổi XYZ, như sau:

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

Cách tiếp cận đầu tiên của tôi không giống nguyên bản lắm - tạo một hàm, như thế này:

/**
 *  @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

Nó hơi dài dòng, nhưng hãy chịu đựng với tôi ở đây. Điều này có lợi thế khi làm việc trong C ++ 03 mà không cần decltype, vì __self_check_helperhàm được sử dụng để suy ra loại this. Ngoài ra, không có static_assert, nhưng sizeof()thủ thuật được sử dụng để thay thế. Bạn có thể làm cho nó ngắn hơn nhiều đối với C ++ 0x. Bây giờ điều này sẽ không hoạt động đối với các mẫu. Ngoài ra, có một vấn đề nhỏ với macro không mong đợi dấu chấm phẩy ở cuối, nếu biên dịch với pedantic, nó sẽ phàn nàn về một dấu chấm phẩy thừa không cần thiết (hoặc bạn sẽ bị bỏ lại với một macro trông kỳ quặc không kết thúc bằng dấu chấm phẩy trong phần thân XYZABC).

Thực hiện kiểm tra đối với những Typegì được chuyển đến DECLARE_SELFkhông phải là một tùy chọn, vì điều đó sẽ chỉ kiểm tra XYZlớp (tốt), không biết đến ABC(có lỗi). Và sau đó nó đánh tôi. Một giải pháp không tốn thêm dung lượng lưu trữ hoạt động với các mẫu:

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 

Điều này chỉ đơn giản là thực hiện xác nhận tĩnh trên một giá trị enum duy nhất (hoặc ít nhất là duy nhất trong trường hợp bạn không viết tất cả mã của mình trên một dòng), không có thủ thuật so sánh kiểu nào được sử dụng và nó hoạt động như xác nhận tĩnh, ngay cả trong các mẫu . Và như một phần thưởng - dấu chấm phẩy cuối cùng bây giờ là bắt buộc :).

Tôi muốn cảm ơn Yakk vì đã cho tôi một nguồn cảm hứng tốt. Tôi sẽ không viết điều này nếu lần đầu tiên nhìn thấy câu trả lời của anh ấy.

Đã thử nghiệm với VS 2008 và g ++ 4.6.3. Thật vậy, với ví dụ XYZABC, nó phàn nàn:

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

Bây giờ nếu chúng ta tạo ABC thành một mẫu:

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

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

Chúng ta sẽ lấy:

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>â

Chỉ kiểm tra số dòng được kích hoạt, vì kiểm tra chức năng không được biên dịch (như mong đợi).

Với C ++ 0x (và không có dấu gạch dưới ác), bạn chỉ cần:

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

Tôi tin rằng đáng tiếc là bit CStaticAssert vẫn được yêu cầu vì nó tạo ra một kiểu, được đánh máy trong phần thân mẫu (tôi cho rằng không thể thực hiện điều tương tự với static_assert). Ưu điểm của phương pháp này là chi phí bằng không.


Về cơ bản bạn đang thực hiện lại static_assertở đây, phải không? Hơn nữa, mã hoàn chỉnh của bạn không hợp lệ vì bạn đang sử dụng số nhận dạng bất hợp pháp (dành riêng).
Konrad Rudolph

@KonradRudolph Vâng, đúng là như vậy. Tôi không có C ++ 0x vào lúc đó, vì vậy tôi đã thực hiện lại static_assert để cung cấp câu trả lời đầy đủ. Tôi nói điều đó trong câu trả lời. Nó không hợp lệ? Bạn có thể chỉ ra làm thế nào? Nó được biên dịch tốt, tôi đang sử dụng nó ngay bây giờ.
the swine

1
Các mã định danh không hợp lệ vì C ++ bảo lưu mọi thứ bằng dấu gạch dưới đứng đầu theo sau là chữ hoa, cũng như hai dấu gạch dưới hàng đầu trong phạm vi toàn cầu, cho trình biên dịch. Mã người dùng không được sử dụng nó, nhưng không phải tất cả các trình biên dịch sẽ gắn cờ nó là lỗi.
Konrad Rudolph

@KonradRudolph Tôi hiểu rồi, tôi không biết điều đó. Tôi có rất nhiều mã sử dụng nó, chưa bao giờ gặp sự cố với nó trên cả Linux / Mac / Windows. Nhưng tôi đoán nó là tốt để biết.
the swine

0

Tôi không biết tất cả về những mẫu kỳ quặc này, còn một thứ siêu đơn giản thì sao:

#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

Công việc đã hoàn thành, trừ khi bạn không thể chịu được một vài macro. Bạn thậm chí có thể sử dụng CLASSNAMEđể khai báo (các) hàm tạo của mình (và tất nhiên là hàm hủy).

Bản demo trực tiếp .


1
Nó có ảnh hưởng khá rõ rệt về cách lớp có thể / phải sau đó được sử dụng
Lightness Races ở Orbit

@LightnessRacesinOrbit Như thế nào? Tôi không thấy nó. Tôi đã xóa câu cuối cùng của bài đăng ban đầu của mình. Những gì tôi có ở đó ban đầu có thể đã khiến bạn nghĩ đến điều này.
Paul Sanders
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.