В следующем примере кода мы наблюдаем странное поведение в GCC. Странным поведением является нарушение ODR в GCC 6.3.0 с типами, определенными в двух отдельных единицах трансляции. Возможно, это связано с определениями рекурсивных типов или неполными типами.
Мы не уверены, действителен ли наш код или мы зависим от неопределенного поведения в способе рекурсивного определения наших типов. Пожалуйста, посмотрите, как вариантный шаблон динамического класса определяется и создается в двух отдельных файлах cpp.
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, Number, 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
Странно, что построение вектора приводит нас прямо к деструктору.
Что очень странно, эти ошибки исчезают, когда мы делаем следующее:
- Переименуйте «Типы» в одном из файлов cpp, чтобы они не использовали одно и то же имя для шаблона класса.
- Сделайте реализацию «Типов» одинаковой в каждом файле cpp (измените Number на удвоение в каждом файле).
- Не помещайте число в вектор.
- Измените реализацию 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>;
};
Мы также видим некоторые предупреждения, связанные с нарушением ODR, когда мы компилируем с оптимизацией времени компоновки (LTO):
dynamic_test.h:51: warning: type ‘struct Dynamic’ violates the C++ One Definition Rule [-Wodr]
struct Dynamic
^
Есть ли у кого-нибудь представление о том, что может вызывать эту проблему?
nm
для них.nm
должен отображать символTypes
в нижнем регистре t. - person Nir Friedman   schedule 05.04.2017Types
из первой ссылки используется во второй ссылке. Я бы не ожидал, что это произойдет с чем-то, что имеет внутреннюю связь, поэтому я спросил о nm. Я пытался сделать репро локально, но мне это не удалось (другая ОС, другой gcc, кто знает). Вы можете попробовать определить статическую строку для каждогоTypes
по-разному и распечатать ее как часть теста, чтобы попытаться доказать это. - person Nir Friedman   schedule 06.04.2017