2016-06-16 2 views
0

Несколько дней назад я смотрел доклад Шона Родитель «Наследование - это базовый класс зла», и я решил попробовать его код. Несмотря на то, сделав несколько изменений, я наткнулся на это странное поведение:Использование команды brace-initialization в списке инициализаторов членов вызывает переполнение стека в std :: vector copy construction (с GCC, но не с Clang)

#include <vector> 
#include <memory> 
using namespace std; 
class object_t { 
    public: 
    template <typename T> 
    object_t(T x) : self_{make_unique<model<T>>(move(x))} {} 
    //explicit object_t(T x) : self_{make_unique<model<T>>(move(x))} {} //this "solves" the problem 
    object_t(const object_t &x) : self_(x.self_->copy_()) {} 

    private: 
    struct concept_t { 
     virtual ~concept_t() = default; 
     virtual unique_ptr<concept_t> copy_() const = 0; 
    }; 
    template <typename T> struct model : concept_t { 
     model(T x) : data_{move(x)} {} //*the behavior is caused by 
             //this brace-initialization 
     //model(T x) : data_(move(x)) {} //this works fine 
     unique_ptr<concept_t> copy_() const override { 
      return make_unique<model<T>>(*this); 
     } 
     T data_; 
    }; 
    unique_ptr<const concept_t> self_; 
}; 

int main() { 
    object_t i{5}; 
    object_t v{vector<int>{1, 2, 3, 4}}; 
    object_t ic{i}; 
    object_t vc{v}; 

    vector<object_t> vv; 
    vector<object_t> vvv1(vv); 
    vector<object_t> vvv2 = vv; 
    vector<object_t> vvv3{vv}; //this fails with a stack overflow in GCC 6.1.1 but only if brace-initialization is used in model<T> 
} 

Я использовал GCC 6.1.1, 3.8.0 и Clang Clang 3.9.0 (совсем недавно сборки). Переполнение стека происходит только тогда, когда код компилируется с помощью GCC и только в том случае, если в конструкторе модели используется инициализация скобок.

Вот выход по адресу Sanitizer в:

ASAN:DEADLYSIGNAL 
================================================================= 
==21125==ERROR: AddressSanitizer: stack-overflow on address 0x7fff8d492ff8 (pc 0x7f41c188eb02 bp 0x000000000020 sp 0x7fff8d493000 T0) 
    #0 0x7f41c188eb01 in __sanitizer::StackDepotBase<__sanitizer::StackDepotNode, 1, 20>::Put(__sanitizer::StackTrace, bool*) /build/gcc-multilib/src/gcc/libsanitizer/sanitizer_common/sanitizer_stackdepotbase.h:96 
    #1 0x7f41c188e657 in __sanitizer::StackDepotPut(__sanitizer::StackTrace) /build/gcc-multilib/src/gcc/libsanitizer/sanitizer_common/sanitizer_stackdepot.cc:110 
    #2 0x7f41c17cffee in __asan::Allocator::Allocate(unsigned long, unsigned long, __sanitizer::BufferedStackTrace*, __asan::AllocType, bool) /build/gcc-multilib/src/gcc/libsanitizer/asan/asan_allocator.cc:420 
    #3 0x7f41c17cffee in __asan::asan_memalign(unsigned long, unsigned long, __sanitizer::BufferedStackTrace*, __asan::AllocType) /build/gcc-multilib/src/gcc/libsanitizer/asan/asan_allocator.cc:703 
    #4 0x7f41c1871df7 in operator new(unsigned long) /build/gcc-multilib/src/gcc/libsanitizer/asan/asan_new_delete.cc:60 
    #5 0x406018 in std::_MakeUniq<object_t::model<std::vector<object_t, std::allocator<object_t> > > >::__single_object std::make_unique<object_t::model<std::vector<object_t, std::allocator<object_t> > >, std::vector<object_t, std::allocator<object_t> > >(std::vector<object_t, std::allocator<object_t> >&&) /usr/include/c++/6.1.1/bits/unique_ptr.h:787 
    #6 0x40356b in object_t::object_t<std::vector<object_t, std::allocator<object_t> > >(std::vector<object_t, std::allocator<object_t> >) /home/theo/test/valuepoly.cpp:16 
    #7 0x409169 in object_t::model<std::vector<object_t, std::allocator<object_t> > >::model(std::vector<object_t, std::allocator<object_t> >) /home/theo/test/valuepoly.cpp:25 

//The same calls repeat again and again... 

    #251 0x406046 in std::_MakeUniq<object_t::model<std::vector<object_t, std::allocator<object_t> > > >::__single_object std::make_unique<object_t::model<std::vector<object_t, std::allocator<object_t> > >, std::vector<object_t, std::allocator<object_t> > >(std::vector<object_t, std::allocator<object_t> >&&) /usr/include/c++/6.1.1/bits/unique_ptr.h:787 
    #252 0x40356b in object_t::object_t<std::vector<object_t, std::allocator<object_t> > >(std::vector<object_t, std::allocator<object_t> >) /home/theo/test/valuepoly.cpp:16 

SUMMARY: AddressSanitizer: stack-overflow /build/gcc-multilib/src/gcc/libsanitizer/sanitizer_common/sanitizer_stackdepotbase.h:96 in __sanitizer::StackDepotBase<__sanitizer::StackDepotNode, 1, 20>::Put(__sanitizer::StackTrace, bool*) 
==21125==ABORTING 

Если я аннотирования конструктор object_t как явная проблема уходит. Я предполагаю, что использование инициализатора brace вызывает gcc, чтобы неявно преобразовать все в object_t и использовать конструктор std :: initializer_list вектора; и это происходит рекурсивно.

Мой вопрос заключается в том, какой из двух компиляторов ведет себя правильно?

+1

FWIW ваш код [совершенно разные] (https://github.com/boostcon/cppnow_presentations_2012/blob/master/fri /value_semantics/value_semantics.cpp#L193) из оригинала на данный момент. Я не удивлюсь, если у вас есть неопределенное поведение. –

+0

Чтобы сделать сниппп короче, я удалил операторы присваивания и переместил конструкторы, поскольку они не имеют никакого значения. Поведение сохраняется независимо. –

ответ

1

Это то же самое, что и List-initialization priority from object of same type; clang является правильным.

[dcl.init.list]/3:

[...] Если T является типом класса и список инициализатора имеет единственный элемент типа сорт U, U, где Т или класс, производный от T, объект инициализируется из этого элемента [...]

+0

Я не думаю, что это то же самое. Использование 'std :: move' здесь явно недействительно? OTOH, этот приоритет, похоже, теперь исправлен для CLang ... – Elijan9

+0

@ Elijan9 ответил на ваш вопрос. – ecatmur

+0

Вы правы, он правильно использует конструктор-аргумент-значение-и-перемещать идиому. К сожалению, эта идиома работает некорректно, поскольку приоритет инициализации списка неверен для GCC (и использовался для CLang). Но использование constructor-argument-by-universal-reference-and-forward будет корректно работать даже в gcc и более старых версиях CLang, правильно? – Elijan9