ฉันจะทำซ้ำวิธีแก้ปัญหาที่ชัดเจนของ "ต้องทำเอง" นี่คือโค้ดเวอร์ชัน 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
) ข้อดีของแนวทางนี้ยังคงเป็นต้นทุนที่เป็นศูนย์
this_t
น่าจะสอดคล้องกับการตั้งชื่อ C ++ ปกติมากกว่า