C++

Using smart pointers and ownership

SagalU 2022. 5. 15. 15:22

Recently, I study rust language and interested in concept of ownership.

For example,

fn main(){
	let s = String::from("Hello, world!");
    display_string(s);
    /*display_string(s);*/ /*Makes compile error because lifetime of s has ended*/
}
fn display_string(_s: String) {
	println!("{}", _s);
} /*Scope has ended and _s is dropped*/

To prevent memory leakage, in rust, " } " the end of a scope invokes drop of all the variables in the scope except return value and borrowed variable. 

 

Similarly, C++ calls the destructor when the scope ends.

For example, 

#include <iostream>
#include <memory>

class ClassInt {
protected:
public:
	int data;
	ClassInt(const int& _init) :data(_init) {};
	~ClassInt() { std::cout<<"This object "<< data <<" has died!\n"; }
	void View() { std::cout << data << std::endl; }
};

std::unique_ptr<ClassInt> GenClassInt(const int& _input) {
	ClassInt* ret1 = new ClassInt(_input);
	auto retval = std::unique_ptr<ClassInt>(ret1);
	return retval; /*Ownership has returned*/
}

int main()
{
	{
		auto ClassInt_in_scope = ClassInt(1); /*It dies when the scope ends*/
		auto P_ClassInt_in_scope = new ClassInt(2); /*It still alives even the scope ends*/
		auto UP_ClassInt_in_scope = std::unique_ptr<ClassInt>(new ClassInt(3)); /*It dies when the scope ends*/
		std::cout << "============Scope ends: Are the objects alive?===========\n";
	}
	std::cout << "============ Out of the scope ===========\n";
	/*delete P_ClassInt_in_scope;*/ /*Compile error: lifetime P_ClassInt_in_scope has ended but the memory is allocated. Memory leakage!*/
    
	return 0;
}

When scope where sample object "1","2", and "3" are generated ends, the destructor is invoked for "1" the normal object and "3" the unique_ptr. However, the destructor is not invoked for "2", and it means the memory leakage.

If the object claims heap memory, we can prevent memory leakage using proper memory deallocation semantic in the destructor except when the object itself is allocated in the heap memory. It is RAII(Resource acquisition is initialization)

For objects allocated in the heap memory, unique_ptr invokes its destructor insteadly.

 

So, what's happen when the function takes unique_ptr?

#include <iostream>
#include <memory>

class ClassInt {
protected:
public:
	int data;
	ClassInt(const int& _init) :data(_init) {};
	~ClassInt() { std::cout<<"This object "<< data <<" has died!\n"; }
	void View() { std::cout << data << std::endl; }
};

void DoSomething1(ClassInt *const _input) {
	/*It just borrows ownership!*/
}
void DoSomething2(std::unique_ptr<ClassInt> _input) {
	/*It takes ownership!*/
} /*Scope ends and lifetime of _input ends, too.*/

std::unique_ptr<ClassInt> GenClassInt(const int& _input) {
	ClassInt* ret1 = new ClassInt(_input);
	auto retval = std::unique_ptr<ClassInt>(ret1);
	return retval; /*Ownership has returned*/
}

int main()
{

	auto p_myint = GenClassInt(3);

	DoSomething1(p_myint.get());
	std::cout << "After DoSomething1, p_myint: ";
	p_myint->View();

	/*DoSomething2(p_myint.get());*/ /*Compile error*/
	DoSomething2(std::move(p_myint)); /*Move ownership to the function*/
	std::cout << "After DoSomething2, p_myint: ";
	p_myint->View();/*Make runtime error since the ownership has moved.*/

	return 0;
}

1. Generate unique_ptr and it is moved to another value out of the scope.(line 23)

It makes no problem. we can use p_myint(line 29) as a pointer. If the return is not stored in any unique_ptr variable. It is destructed automatically.

 

2. Function takes pointer as argument(line 13, 31)

To use unique_ptr as pointer, we should use .get() method.

End of the function(line 15) does not mean end of lifetime the variable. So, our p_myint is usable yet.

 

3. Function takes unique_ptr as argument(line 36, 20)

To use unique_ptr as argument of the function, we should std::move() since unique_ptr cannot be copied but can be moved in to the function.

End of the function(line 18) means end of lifetime of the unique_ptr, so it is destructed. Hence, reusing of it(line 38) makes runtime error.

But if p_myint has regenerated, we can use it again.