在封装指针的类中,移动语义无意中被复制构造函数取代
Move semantic unintentionally superseded by copy constructor in class that encapsulates pointer
我制作了一个与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));
有一个命名成员变量的明显副本。
- 如何从具有移动语义的类对象中生成共享指针
- 在C++17中,引用const字符串的语义应该是什么
- 了解构造函数在移动、复制、赋值语义中的行为
- 在C++中使用移动语义的正确方法是什么?
- 在 C++11 中移动语义
- 复制省略并在返回值中移动语义
- 在一个微不足道的可复制结构中,移动语义应该实现吗?
- 在理解移动语义的同时混淆 c++ 程序中的操作
- 为什么移动语义与动态 mem 分配中的浅拷贝具有相同的行为
- 如果没有带有函数签名的 rvalue 参数,是否会执行 C++ 11 中的移动语义?
- 阵列中的元素一直无意中变化
- 使用膨胀卷积的语义分割中的上采样
- 使用 Move 语义在构造函数中初始化类成员
- 标准库中是否有与 std::thread 的构造函数语义匹配的类型擦除函数包装器?
- 支持向量中的移动语义
- 由于libkern.h中的语义问题,Xcode Build失败
- 在封装指针的类中,移动语义无意中被复制构造函数取代
- 无意中复制参数
- 为什么我的琴弦在无意中发生了变化
- 无意中修改了对象的副本