Несколько дней назад я смотрел доклад Шона Родитель «Наследование - это базовый класс зла», и я решил попробовать его код. Несмотря на то, сделав несколько изменений, я наткнулся на это странное поведение:Использование команды 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 вектора; и это происходит рекурсивно.
Мой вопрос заключается в том, какой из двух компиляторов ведет себя правильно?
FWIW ваш код [совершенно разные] (https://github.com/boostcon/cppnow_presentations_2012/blob/master/fri /value_semantics/value_semantics.cpp#L193) из оригинала на данный момент. Я не удивлюсь, если у вас есть неопределенное поведение. –
Чтобы сделать сниппп короче, я удалил операторы присваивания и переместил конструкторы, поскольку они не имеют никакого значения. Поведение сохраняется независимо. –