在成员初始化器列表中使用大括号初始化会导致 std::vector 复制构造中的堆栈溢出(使用 GCC 但不使用 Clang)

Using brace-initialization in member initializer list causes stack overflow in std::vector copy construction (with GCC but not with Clang)

几天前,我观看了 Sean Parent 的演讲 "Inheritance is the Base Class of Evil",我决定试用他的代码。在进行一些更改时,我偶然发现了这种奇怪的行为:

#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、Clang 3.8.0 和 Clang 3.9.0(最新版本)。只有在使用 GCC 编译代码并且在模型的构造函数中使用大括号初始化时才会发生堆栈溢出。

这是地址清理程序的输出:

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 的构造函数注释为显式,问题就会消失。我猜想使用大括号初始化器会导致 gcc 将所有内容隐式转换为 object_t 并使用 vector 的 std::initializer_list 构造函数;这是递归发生的。

我的问题是这两个编译器中哪个行为正确?

这与相同; clang 是正确的。

[dcl.init.list]/3:

[...] If T is a class type and the initializer list has a single element of type cv U, where U is T or a class derived from T, the object is initialized from that element [...]