Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

可以借助解引用操作变通地对 std::unique_ptr 进行深拷贝 #32

Open
ltimaginea opened this issue Jan 7, 2022 · 2 comments

Comments

@ltimaginea
Copy link

ltimaginea commented Jan 7, 2022

// std::unique_ptr<MyTime> mt3 = mt1; // error

课件这里,其实可以这样进行拷贝: std::unique_ptr<MyTime> mt3 = std::make_unique<MyTime>(*mt1);

虽然 std::unique_ptr 删除了 copy constructor 和 copy assignment operator ,但其实我们可以借助解引用操作变通地对 std::unique_ptr 进行拷贝。

deep copy 示例如下:

std::unique_ptr<std::string> up1(std::make_unique<std::string>("Good morning"));

// copy construct!
std::unique_ptr<std::string> up2(std::make_unique<std::string>(*up1));
// safe copy construct!
std::unique_ptr<std::string> up3(up1 ? std::make_unique<std::string>(*up1) : nullptr);
// copy assignment!
up2 = std::make_unique<std::string>(*up1);
// safe copy assignment!
up3 = up1 ? std::make_unique<std::string>(*up1) : nullptr;

其它的例证:

@ltimaginea ltimaginea changed the title 可以借助移动操作变通地对 unique_ptr 进行深拷贝 可以借助移动操作变通地对 std::unique_ptr 进行深拷贝 Jan 7, 2022
@ltimaginea ltimaginea changed the title 可以借助移动操作变通地对 std::unique_ptr 进行深拷贝 可以借助解引用操作变通地对 std::unique_ptr 进行深拷贝 Feb 8, 2022
@CutieDeng
Copy link

报歉,使用指针的过程中进行拷贝是不合时宜的操作。

能够进行 trivial copy 的类型没有必要用指针管理。

以下 Demo 给出了一个错误场景下使用 dereference op 进行 deep copy 的例子。

#include <iostream> 
#include <memory> 

// You shouldn't get an instance of it! 
class UnsafeClass {
    public: 
        virtual void do_nothing() const {
            printf ("[[UNSAFE OP!!!]]\n");
        }
}; 

class Derived : public UnsafeClass {
    public: 
        void do_nothing() const override {
            printf ("Derived: do nothing. \n");    
        }
}; 

int main() {
    using namespace std; 
    unique_ptr < UnsafeClass > ptr = make_unique < Derived > (); 
    // tell me, what would happens when you call it! 
    ptr->do_nothing(); 
    // do a copy! 
    unique_ptr < UnsafeClass > ptr2 = make_unique < decltype(ptr) :: element_type > ( *ptr ); 
    // successfully? or not? 
    ptr2->do_nothing(); 
}

@ltimaginea
Copy link
Author

ltimaginea commented Oct 21, 2022

@CutieDeng

Hi,朋友,感谢交流。

关于上条评论中,你的代码的不足之处在于:

关于多态类型的深拷贝,方法是使用设计模式中的prototype(原型模式),典型实现如下:

#include <memory>
#include <typeinfo>
#include <cassert>

class Prototype
{
public:
	virtual std::unique_ptr<Prototype> Clone() const = 0;

	virtual ~Prototype() = default;

protected:
	Prototype() = default;

	// Make the polymorphic base class copy and move operations protected to prevent slicing, 
	// and so that only the derived class can invoke them in its own copy and move operations.
	Prototype(const Prototype&) = default;
	Prototype(Prototype&&) = default;
	Prototype& operator=(const Prototype&) = default;
	Prototype& operator=(Prototype&&) = default;
};

class Derived : public Prototype
{
public:
	std::unique_ptr<Prototype> Clone() const override
	{
		return std::make_unique<Derived>(*this);
	}
};

int main()
{
	std::unique_ptr<Prototype> p = std::make_unique<Derived>();
	std::unique_ptr<Prototype> q = p->Clone();

	assert((p != q) && (typeid(*p) == typeid(*q)) && (typeid(*q) == typeid(Derived)));

	return 0;
}

See also

prototype的一个典型应用是用来实现 polymorphic_value(多态值语义),关于 polymorphic_value :

#include <iostream>
#include <vector>
#include <array>
#include <iterator>
#include <memory>
#include <utility>
#include <typeinfo>
#include <cassert>

class Shape
{
public:
	std::unique_ptr<Shape> Clone() const
	{
		std::unique_ptr<Shape> result = DoClone();
		assert(typeid(*result) == typeid(*this) && "Every derived class must correctly override DoClone.");
		return result;
	}

	virtual void Draw() = 0;

	virtual ~Shape() = default;

protected:
	Shape() = default;

	// Make the polymorphic base class copy and move operations protected to prevent slicing, 
	// and so that only the derived class can invoke them in its own copy and move operations.
	Shape(const Shape&) = default;
	Shape(Shape&&) = default;
	Shape& operator=(const Shape&) = default;
	Shape& operator=(Shape&&) = default;

private:
	virtual std::unique_ptr<Shape> DoClone() const = 0;
};

class Circle : public Shape
{
public:
	void Draw() override
	{
		std::cout << "Draw a circle." << std::endl;
	}

private:
	std::unique_ptr<Shape> DoClone() const override
	{
		return std::make_unique<Circle>(*this);
	}
};

class Square : public Shape
{
public:
	void Draw() override
	{
		std::cout << "Draw a square." << std::endl;
	}

private:
	std::unique_ptr<Shape> DoClone() const override
	{
		return std::make_unique<Square>(*this);
	}
};

// a simple demo implementation of polymorphic_value
class Picture
{
public:
	explicit Picture(std::unique_ptr<Shape> shape) : shape_(std::move(shape)) {  }

	Picture(const Picture& rhs) : shape_(rhs.shape_->Clone()) {  }

	Picture& operator=(const Picture& rhs)
	{
		if (this != &rhs)
		{
			shape_ = rhs.shape_->Clone();
		}
		return *this;
	}

	Picture(Picture&&) = default;
	Picture& operator=(Picture&&) = default;
	~Picture() = default;

	void ChangeShape(std::unique_ptr<Shape> shape)
	{
		shape_ = std::move(shape);
	}

	void Draw()
	{
		shape_->Draw();
	}

private:
	std::unique_ptr<Shape> shape_;
};

int main()
{
	Picture picture1(std::make_unique<Circle>());
	picture1.Draw();
	Picture picture2(std::make_unique<Square>());
	picture2.Draw();

	Picture picture3(picture1); // OK: copyable
	picture3.Draw();
	picture3 = picture2; // OK: copyable
	picture3.Draw();

	Picture picture4(std::move(picture1));
	picture4.Draw();
	picture4 = std::move(picture2);
	picture4.Draw();

	Picture picture5(std::make_unique<Circle>());
	picture5.Draw();
	picture5.ChangeShape(std::make_unique<Square>());
	picture5.Draw();

	std::vector<Picture> pictures1;
	pictures1.emplace_back(std::make_unique<Circle>());
	pictures1.emplace_back(std::make_unique<Square>());
	for (auto& picture : pictures1)
	{
		picture.Draw();
	}

	std::vector<std::unique_ptr<Shape>> shapes1;
	shapes1.push_back(std::make_unique<Circle>());
	shapes1.push_back(std::make_unique<Square>());
	for (auto& shape : shapes1)
	{
		shape->Draw();
	}

	std::vector<Picture> pictures2{ picture3, picture4, picture5 };
	std::vector<Picture> pictures3(pictures1); // OK: copyable
	std::vector<Picture> pictures4(std::move(pictures1));
	pictures3 = pictures2; // OK: copyable
	pictures4 = std::move(pictures2);

	std::vector<std::unique_ptr<Shape>> shapes2;
	shapes2.push_back(std::make_unique<Circle>());
	shapes2.push_back(std::make_unique<Circle>());
	shapes2.push_back(std::make_unique<Circle>());

	auto init = std::to_array<std::unique_ptr<Shape>>({ std::make_unique<Circle>(), std::make_unique<Square>() });
	std::vector<std::unique_ptr<Shape>> shapes3(std::make_move_iterator(init.begin()), std::make_move_iterator(init.end()));

	//std::vector<std::unique_ptr<Shape>> shapes4(shapes1); // Error: noncopyable
	std::vector<std::unique_ptr<Shape>> shapes5(std::move(shapes1));
	//shapes3 = shapes2; // Error: noncopyable
	shapes5 = std::move(shapes2);

	return 0;
}

解释说明:

  • 实现要点在于,代理类Picture的拷贝构造函数和拷贝赋值运算符的实现中通过调用克隆函数进行多态深拷贝,通过这样的实现,代理类Picture成为了一个值语义多态类(而非指针语义多态),因此当我们对Picture类对象进行拷贝构造或拷贝赋值时,将会发生多态深拷贝,即深拷贝对应的派生类对象,如上面代码中的 picture3pictures2pictures3
  • 因为上面的代码中使用了 std::to_array ,因此需要以C++20标准进行编译。上面的代码中,之所以 shapes3 不能像 pictures2 那样进行列表初始化,是因为 std::initializer_list 的底层实现是一个 const T[N] 类型的数组,导致了我们不能对它进行移动来窃取资源,又或见 c++ - Can I list-initialize a vector of move-only type? - Stack Overflow

See also

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants