在封装指针的类中,移动语义无意中被复制构造函数取代

Move semantic unintentionally superseded by copy constructor in class that encapsulates pointer

本文关键字:无意中 语义 复制 取代 构造函数 移动 指针 封装      更新时间:2023-10-16

我制作了一个与std::unique_ptr非常相似的模板类derived_object<T>。虽然T只是实际对象的基类,但它还有一个附加功能,即可以进行深度复制。该技术取自boost::any。以下是文件derived_object.h:的(不幸的是很长)内容

#pragma once
#include <type_traits>
#include <algorithm>
template<class Base>
class derived_object
{
public:
derived_object()
: content(nullptr)
{}
template<typename Derived>
derived_object(Derived* ptr)
: content(new holder<Derived>(ptr))
{
static_assert(std::is_copy_constructible<Derived>::value, "type of pointer must have copy constructor");
static_assert(std::is_base_of<Base, Derived>::value, "type of pointer must be derived from base type");
}
derived_object(derived_object const& other); // left unimplemented for testing
//      : content(other.content ? other.content->clone() : nullptr)
//  {}
derived_object& operator=(derived_object const& rhs); // left unimplemented for testing
//  {
//      derived_object<Base>(rhs).swap(*this);
//      return *this;
//  }
derived_object(derived_object&& other) noexcept
: content(other.content)
{
other.content = nullptr;
}
derived_object& operator=(derived_object&& rhs) noexcept
{
rhs.swap(*this);
derived_object<Base>().swap(rhs);
return *this;
}
~derived_object()
{
delete content;
}
Base* operator->() const
{
return content->operator Base*();
}
Base* get() const
{
return content->operator Base*();
}
derived_object& swap(derived_object& rhs) noexcept
{
std::swap(content, rhs.content);
return *this;
}
private:
class placeholder
{
public:
virtual ~placeholder() {}
virtual operator Base*() const = 0;
virtual placeholder* clone() const = 0;
};
template<typename Derived>
class holder : public placeholder
{
Derived* held;
public:
holder(Derived* der_ptr)
: held(der_ptr)
{ }
placeholder* clone() const override
{
return new holder(new Derived(*held));
}
~holder() override
{
delete held;
}
operator Base*() const override { return held; }
};
placeholder* content;
};

总的来说,这很好用。但在一个特定的情况下,我想使用移动语义而不是复制,我的程序失败了。出于测试目的,我通过声明但不定义相应的方法来停用derived_object中的复制。

以下是classes.h的内容

#pragma once
#include "derived_object.h"
class B1
{};
class D1 : public B1
{};

class B2
{};
class D2 : public B2
{
public:
D2(derived_object<B1> member);
derived_object<B1> member;
};

classes.cpp

#include "classes.h"
D2::D2(derived_object<B1> m)
:   member(std::move(m))
{}

main.cpp

#include "classes.h"
int main()
{
derived_object<B2> e(new D2(new D1()));
}

通过使用Clang(版本3.8.0-2ubuntu4)进行编译

clang++ -std=c++14 -o main.cpp.o -c main.cpp
clang++ -std=c++14 -o classes.cpp.o -c classes.cpp
clang++ main.cpp.o classes.cpp.o -o output

给出链接器错误CCD_ 9。

但是不应该复制derived_object,我总是移动或使用构造函数derived_object(Derived* ptr)

奇怪的是,当我内联声明D2构造函数或进行其他微小的更改时,错误并没有发生
为什么编译器要复制我的对象,尽管移动似乎是可能的
如何更改derived_object.h(除了更改其方法签名)?

更改

derived_object& operator=(derived_object const& rhs);
derived_object(derived_object const& other);

derived_object& operator=(derived_object const& rhs)=delete;
derived_object(derived_object const& rhs)=delete;

如果使用的话,这将在编译时而不是链接时给您带来错误。

当我这样做(实际例子)时,我在这里得到了一个明显的错误:

static_assert(std::is_copy_constructible<Derived>::value, "type of pointer must have copy constructor");

完全错误:

main.cpp:16:9: error: static_assert failed "type of pointer must have copy constructor"
static_assert(std::is_copy_constructible<Derived>::value, "type of pointer must have copy constructor");
^             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
main.cpp:109:24: note: in instantiation of function template specialization 'derived_object<B2>::derived_object<D2>' requested here
derived_object<B2> e(new D2(new D1()));
^
main.cpp:76:35: error: call to implicitly-deleted copy constructor of 'D2'
return new holder(new Derived(*held));
^       ~~~~~
main.cpp:71:9: note: in instantiation of member function 'derived_object<B2>::holder<D2>::clone' requested here
holder(Derived* der_ptr)
^

这就把我指向这里:

placeholder* clone() const override
{
return new holder(new Derived(*held));
}

您会注意到您在此处复制了*held,而且您还打算复制*held

此处创建包含clone的类:

content(new holder<Derived>(ptr))

然后调用D2的复制构造函数,该复制构造函数隐式地复制derived_object类型的member

如果包含derived_object的复制构造函数但未实现,则生成的D2的复制构造函数中的derived_object的隐式副本会导致链接错误。它在derived_object<D2>中创建holder<D2>类型时使用。

虚拟函数即使从未被调用,也会被实现和编译。

然后我们实现两种方法:

derived_object(derived_object const& other):
content(other.content?other.content->clone():nullptr)
{}
derived_object& operator=(derived_object const& rhs) {
auto tmp = rhs;
return (*this)=std::move(tmp);
}

一切都很好(实例)。

请注意,深度复制不是基于这里的基本类型进行复制。我们有实际派生类型的类型擦除克隆,而不是基类型。

或者,为了确认没有完成愚蠢的复制,我们都保留未实现的副本assign和ctor,并删除clone。这也编译(实际示例)。

删除复制构造函数(使用= delete语法)而不是未定义它应该会从编译器中给您一个更相关的提示,应该会引起您的注意:

return new holder(new Derived(*held));

有一个命名成员变量的明显副本。