Probe smart pointer again

Using any tool, you need to bear the cost and risk.

1. Don't use two shared for a bare pointer_ PTR management, unique_ptr is the same. They all think they own this resource. Try it when you release it.

2. Use weak_ptr breaks circular references.

3. When you need to use this as a smart pointer in the internal interface of a class, you need to use this class to derive from enable_shared_from_this.
enable_shared_from_this and shared_from_this cannot be used in construction and deconstruction, or in some cases, because the class has not been generated well at the time of construction, and it is meaningless that the class is almost finished at the time of deconstruction.

#include <cassert>
#include <memory>
#include <iostream>

class Parent;

typedef std::shared_ptr<Parent> ParentPtr;
typedef std::weak_ptr<Parent> WeakParentPtr;

class Child : public std::enable_shared_from_this<Child> {
public:
	WeakParentPtr father;
	~Child();
	Child();
	void checkRelation();
};

typedef std::shared_ptr<Child> ChildPtr;
typedef std::weak_ptr<Child> WeakChildPtr;

class Parent : public std::enable_shared_from_this<Parent> {
public:
	WeakChildPtr son;
	~Parent();
	Parent();
	void checkRelation();
};

void handleChildAndParentRef(const Parent& p, const Child& c) {
	auto cp = c.father.lock();
	auto pc = p.son.lock();
	if (cp.get() == &p && pc.get() == &c) {
		std::cout << "right relation\n";
	}
	else {
		std::cout << "oop!!!!!\n";
	}
}

void handleChildAndParent(const ParentPtr& p, const ChildPtr& c) {
	auto cp = c->father.lock();
	auto pc = p->son.lock();
	if (cp == p && pc == c) {
		std::cout << "right relation\n";
	}
	else {
		std::cout << "oop!!!!!\n";
	}
}

Child::Child() { std::cout << "hello child\n"; }
Parent::Parent() { std::cout << "hello parent\n"; }
Child::~Child() { std::cout << "bye child\n"; }
Parent::~Parent() { std::cout << "bye parent\n"; }

void Parent::checkRelation() {
	auto ps = son.lock();
	if (ps) {
		// this
		handleChildAndParent(shared_from_this(), ps);
	}
	std::cout << "after call checkRelation\n";
}

void Child::checkRelation() {
	// we call handleChildAndParent
}

void testParentAndChild() {
	Parent pp;
	ParentPtr p(new Parent());
	ChildPtr c(new Child());
	p->son = c; // c.use_count() == 2 and p.use_count() == 1
	c->father = p; // c.use_count() == 2 p.use_count() == 2
	p->checkRelation();
}

int main() {
	testParentAndChild();
}

4,shared_ptr,weak_ptr is much larger than bare pointers and has an impact on efficiency, especially in multithreaded mode.

On this point, you can realize a smart pointer by yourself.

A shared_ptr is at least the size of three bare pointers (24 bytes) in space. It has its own reference and needs to cooperate with weak_ptr is used, so how many references to the pointer should be saved. Generate the new of the existing object as well as its own as follows:

ObjectPtr obj3(new Object(2));

ObjectPtr obj4 = obj3 is much slower when copying
You can reduce space in the following ways

ObjectPtr obj5 = std::make_shared<Object>(3);
//ObjectPtr obj5(new Object(3));

5. If possible, use instances of classes first, and then STD:: unique as a last resort_ PTR, use STD: shared as a last resort_ ptr.

Use unique_ptr and shared_ Like PTR, in order to prevent the situation that delete cannot be called to release resources when handling some exceptions, there will only be a unique at a specific time_ PTR is used to manage a resource. There is no sharing mode, so the copy constructor, = symbol copy operation, etc. do not exist.

Because unique_ptr is unique. To transfer resources, you can transfer values. You can only call the right value reference of obj instead of the left value. The transfer function is released after execution. After calling transfer, the original pointer will become a null pointer and will no longer be managed.

#include <cassert>
#include <memory>
#include <iostream>

typedef int Object;

typedef std::unique_ptr<Object> UniqueObjectPtr;
typedef std::shared_ptr<Object> SharedObjectPtr;

void print(const UniqueObjectPtr& obj) {}

void transfer(UniqueObjectPtr obj) {
	std::cout << obj << std::endl;
}

void uniquePtr() {
	UniqueObjectPtr obj(new Object(1));
	auto p = obj.get(); // operator bool
	if (p) {
		std::cout << p << std::endl;
	}
	// better
	if (obj) {
		std::cout << obj << std::endl;
	}

	// operator -> *
	std::cout << p << obj << std::endl;

	p = obj.release();
	delete p;

	obj.reset();
	obj.reset(new Object(2)); // obj.reset();
	// UniqueObjectPtr(const UniqueObjectPtr&) = delete
	// UniqueObjectPtr(UniqueObjectPtr&&) = default
	transfer(std::move(obj));

	assert(obj == nullptr);
	//std::cout << obj->id() << std::endl;

	obj.reset(new Object(4));
	SharedObjectPtr sharedObj(std::move(obj));
	assert(obj == nullptr);
	// shared_ptr weak_ptr enable_shared_from_this
	// unique_ptr
}

6. Smart pointers can only represent ownership. If you encounter some complex data structures or scenes with unclear ownership, you still have to use naked pointers.
It's also a good example. Try writing a binary tree with a smart pointer.

Well, that's it. I'm afraid I won't use it in the future.
The reason why I want to write such an article today is that recently I have seen the hype about the benefits of smart pointers, which makes me a little want to change all the code of naked pointers into smart pointers.
But don't forget our first sentence.

Tags: C++

Posted on Sun, 17 Oct 2021 21:45:55 -0400 by Jaquio