dynamic_test.h:
#pragma once #include <algorithm> #include <type_traits> namespace dynamic { template <class T> void erasure_destroy( const void *p ) { reinterpret_cast<const T*>( p )->~T(); } template <class T> void erasure_copy( void *pDest,const void *pSrc ) { ::new( pDest ) T( *reinterpret_cast<const T*>( pSrc ) ); } template <class T> struct TypeArg {}; struct ErasureFuncs { template <class T = ErasureFuncs> ErasureFuncs( TypeArg<T> t = TypeArg<T>() ) : pDestroy( &erasure_destroy<T> ),pCopy( &erasure_copy<T> ) { (void)t; } std::add_pointer_t<void( const void* )> pDestroy; std::add_pointer_t<void( void*,const void* )> pCopy; }; enum class TypeValue { Null,Number,Vector }; template <typename T> using unqual = std::remove_cv_t<std::remove_reference_t<T>>; template <class Base,class Derived> using disable_if_same_or_derived = std::enable_if_t<!std::is_base_of<Base,unqual<Derived>>::value>; template <template <class> class TypesT> struct Dynamic { using Types = TypesT<Dynamic>; using Null = typename Types::Null; using Number = typename Types::Number; using Vector = typename Types::Vector; Dynamic() { construct<Null>( nullptr ); } ~Dynamic() { m_erasureFuncs.pDestroy( &m_data ); } Dynamic( const Dynamic &d ) : m_typeValue( d.m_typeValue ),m_erasureFuncs( d.m_erasureFuncs ) { m_erasureFuncs.pCopy( &m_data,&d.m_data ); } Dynamic( Dynamic &&d ) = delete; template <class T,class = disable_if_same_or_derived<Dynamic,T>> Dynamic( T &&value ) { construct<unqual<T>>( std::forward<T>( value ) ); } Dynamic &operator=( const Dynamic &d ) = delete; Dynamic &operator=( Dynamic &&d ) = delete; private: static TypeValue to_type_value( TypeArg<Null> ) { return TypeValue::Null; } static TypeValue to_type_value( TypeArg<Number> ) { return TypeValue::Number; } static TypeValue to_type_value( TypeArg<Vector> ) { return TypeValue::Vector; } template <class T,class...Args> void construct( Args&&...args ) { m_typeValue = to_type_value( TypeArg<T>() ); m_erasureFuncs = TypeArg<T>(); new ( &m_data ) T( std::forward<Args>( args )... ); } private: TypeValue m_typeValue; ErasureFuncs m_erasureFuncs; std::aligned_union_t<0,Null,Vector> m_data; }; } void test1(); void test2();
dynamic_test_1.cpp:
#include "dynamic_test.h" #include <vector> namespace { template <class DynamicType> struct Types { using Null = std::nullptr_t; using Number = long double; using Vector = std::vector<DynamicType>; }; using D = dynamic::Dynamic<Types>; } void test1() { D::Vector v1; v1.emplace_back( D::Number( 0 ) ); }
dynamic_test_2.cpp:
#include "dynamic_test.h" #include <vector> namespace { template <class DynamicType> struct Types { using Null = std::nullptr_t; using Number = double; using Vector = std::vector<DynamicType>; }; using D = dynamic::Dynamic<Types>; } void test2() { D::Vector v1; v1.emplace_back( D::Number( 0 ) ); }
main.cpp中:
#include "dynamic_test.h" int main( int,char* const [] ) { test1(); test2(); return 0; }
运行此代码会导致SIGSEGV具有以下堆栈跟踪:
1 ?? 0x1fa51 2 dynamic::Dynamic<(anonymous namespace)::Types>::~Dynamic dynamic_test.h 66 0x40152b 3 std::_Destroy<dynamic::Dynamic<(anonymous namespace)::Types>> stl_construct.h 93 0x4013c1 4 std::_Destroy_aux<false>::__destroy<dynamic::Dynamic<(anonymous namespace)::Types> *> stl_construct.h 103 0x40126b 5 std::_Destroy<dynamic::Dynamic<(anonymous namespace)::Types> *> stl_construct.h 126 0x400fa8 6 std::_Destroy<dynamic::Dynamic<(anonymous namespace)::Types> *,dynamic::Dynamic<(anonymous namespace)::Types>> stl_construct.h 151 0x400cd1 7 std::vector<dynamic::Dynamic<(anonymous namespace)::Types>>::~vector stl_vector.h 426 0x400b75 8 test2 dynamic_test_2.cpp 20 0x401796 9 main main.cpp 6 0x400a9f
构造一个Vector直接将我们带到析构函数是很奇怪的.
非常奇怪的是,当我们执行以下操作时,这些错误会消失:
>重命名其中一个cpp文件中的“类型”,以便它们不使用
类模板的名称相同.
>在每个cpp文件中使“类型”的实现相同(更改
每个文件中要加倍的数字).
>不要将数字推送到矢量.
>更改Dynamic的实现以不使用此递归类型
定义风格.
以下是一个有效的实现示例:
template <class Types> struct Dynamic { using Null = typename Types::Null; using Number = typename Types::Number; using Vector = typename Types::template Vector<Dynamic>; ... struct Types { using Null = std::nullptr_t; using Number = long double; template <class DynamicType> using Vector = std::vector<DynamicType>; };
当我们使用链接时优化(LTO)进行编译时,我们还会看到一些与ODR违规相关的警告:
dynamic_test.h:51: warning: type ‘struct Dynamic’ violates the C++ One Definition Rule [-Wodr] struct Dynamic ^
有没有人对可能导致此问题的原因有所了解?
解决方法
#include "header.h" #include <iostream> namespace { template <class T> struct Foo { static int foo() { return 1; }; }; using D = Bar<Foo>; } void test1() { std::cerr << "Test1: " << D::foo() << "\n"; }
现在,test2.cpp与此完全相同,只是Foo :: foo返回2,底部声明的函数称为test2并打印Test2:依此类推.接下来,header.h:
template <template <class> class TT> struct Bar { using type = TT<Bar>; static int foo() { return type::foo(); } }; void test1(); void test2();
最后,main.x.cpp:
#include "header.h" int main() { test1(); test2(); return 0; }
您可能会惊讶地发现此程序打印:
Test1: 1 Test2: 1
当然,这只是因为我编译:
g++ -std=c++14 main.x.cpp test1.cpp test2.cpp
如果我颠倒了最后两个文件的顺序,它们都打印2.
发生的事情是链接器最终使用它遇到Foo时遇到的第一个定义.嗯,但我们在一个匿名命名空间中定义了Foo,它应该给它内部链接,避免这个问题.所以我们只编译一个TU然后使用nm:
g++ -std=c++14 -c test1.cpp nm -C test1.o
这产生以下结果:
U __cxa_atexit U __dso_handle 0000000000000087 t _GLOBAL__sub_I__Z5test1v 0000000000000049 t __static_initialization_and_destruction_0(int,int) 0000000000000000 T test1() 000000000000003e t (anonymous namespace)::Foo<Bar<(anonymous namespace)::Foo> >::foo() 0000000000000000 W Bar<(anonymous namespace)::Foo>::foo() U std::ostream::operator<<(int) U std::ios_base::Init::Init() U std::ios_base::Init::~Init() U std::cerr 0000000000000000 r std::piecewise_construct 0000000000000000 b std::__ioinit U std::basic_ostream<char,std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char,std::char_traits<char> >&,char const*)
除了大写与小写之外,现在不要担心这些字母.小写符号是私有的,我们期望内部链接符号的方式.大写符号是公共的,并且具有外部链接并且暴露给链接器.
有趣的是,并不是那么令人惊讶,虽然Foo可能有内部联系,Bar却没有!第一个翻译单元已经定义了符号Bar< Foo>.与外部联系.第二个翻译单元也是这样做的.因此,当链接器链接它们时,它会看到两个转换单元尝试使用外部链接定义相同的符号.请注意,它是内联定义的类成员,因此它是隐式内联的.所以链接器一如既往地处理它:它只是静默地删除它在第一个之后运行的所有定义(因为符号已经被定义;这就是链接器如何工作,从左到右).So Foo在每个TU中被正确定义,但Bar< Foo>不是.
底线是这是ODR违规.你想重新思考一些东西.
编辑:事实上似乎这是gcc中的一个错误.标准的措辞意味着在这种情况下应该唯一地对待Foos,因此每个Foo上模板化的条形应该是分开的.链接到bug:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70413.