为什么我的 shared_ptr 能够从原始指针隐式构造?

Why is my shared_ptr able to construct implicitly from a raw pointer?

我正在我的应用程序 std::shared_ptr 下放置一个 class,转换现有代码。我的理解是 shared_ptr 的原始指针的构造函数是显式的(例如 http://www.cplusplus.com/reference/memory/shared_ptr/shared_ptr/),这对我来说很有意义。但是,我在我的应用程序中看到一件奇怪的事情,在我看来,构造函数的行为就好像它是隐式的一样。我不明白这怎么可能。

首先,我的 shared_ptr 类型有一个 typedef

typedef std::shared_ptr<EidosPropertySignature const> EidosPropertySignature_CSP;

然后我有一些代码可以构造一个新对象并将其添加到共享指针向量中:

std::vector<EidosPropertySignature_CSP> properties;

properties.emplace_back(new EidosPropertySignature(...constructor parameters...));

编译时甚至没有警告。我不明白 - new EidosPropertySignature 的结果是一个 EidosPropertySignature *,它不应该隐式转换为向量持有的 EidosPropertySignature_CSP,对吧?我非常希望这个构造产生一个编译错误,正如我所想的那样。为什么不可以,有没有办法修改我的方法以使其可以?

这里没有隐式转换。 emplace_back 将其参数直接转发给元素类型的构造函数以构造新元素 [sequence.reqmts]:

Appends an object of type T constructed with std​::​forward<​Args​>(​args)....

因此,emplace_back本质上是一个直接的构造函数调用。另一方面,push_back 将不起作用,因为这实际上需要转换……

在纯粹的概念层面上,隐式转换是关于允许将某种类型的值转换为不同类型的值如有必要,可能作为更长的一部分转换序列。转换是关于搭建桥梁。通过隐式转换,您告诉编译器:这是从 AB 的桥梁,您可以使用它来解决问题。通过显式转换,您说:这是从 AB 的桥梁,仅当我明确告诉您转到 B 时才使用它;特别是,如果您只是想前往 B 以外的其他地方,请不要使用它。当你写 emplace_back 时,你 明确地 说 "construct an element"。 emplace_back 显式表示创建元素类型的对象。您 明确地 告诉编译器转到 B。关于 emplace_back

没有任何暗示

如果你看std::vector::emplace_back

The element is constructed through std::allocator_traits::construct, which typically uses placement-new to construct the element in-place at the location provided by the container. The arguments args... are forwarded to the constructor as std::forward<Args>(args)....

幕后发生的事情本质上是:

new (storage) EidosPropertySignature_CSP(<emplace_back args>)

调用EidosPropertySignature_CSP的显式构造函数。

emplace_back 直接根据传递的参数就地构造元素。

The element is constructed through std::allocator_traits::construct, which typically uses placement-new to construct the element in-place at the location provided by the container.

注意std::allocator_traits<Alloc>::construct constructs the element as ::new (static_cast<void*>(p)) T(std::forward<Args>(args)...) at last, which is direct initialization;元素类型的构造函数是explicit与否无关紧要。

(强调我的)

Direct-initialization is more permissive than copy-initialization: copy-initialization only considers non-explicit constructors and non-explicit user-defined conversion functions, while direct-initialization considers all constructors and all user-defined conversion functions.

另一方面,push_back takes shared_ptr as parameter type, when a raw pointer being passed it has to be converted to shared_ptr implicitly, which is ill-formed because explicit converting constructor won't be considered in copy initialization.

因为使用emplace_back,它不是隐式的。

该函数看起来像 template<class... Args> reference emplace_back(Args&&... args),所以在这种情况下,您有一个参数,EidosPropertySignature *,所以没问题。

然后std::shared_ptr构造器会被std::forward<Args>(args)...调用,可以调用显式构造器

template<class T, class... Args> T construct(Args...args)
{
    return T(std::forward<Args>(args)...);
}
int main()
{
    int *x = new int();
    std::shared_ptr<int> a = x; // error 
    std::shared_ptr<int> b(std::forward<int*>(x)); // OK
    std::shared_ptr<int> c = construct<std::shared_ptr<int>>(x); // OK
}