C++ Smart Pointer

LeemHyungJun·2022년 9월 18일
0

C++ Study

목록 보기
5/12
post-thumbnail

예시코드)
https://github.com/GbLeem/CPP_Study/tree/main/NoCode
39.cpp ~ 42.cpp

Smart Pointer

  • unique_ptr, shared_ptr, weak_ptr
  • 사용 이유 : Resource Acquisition Is Initialization(RAII)
    • object와 resource의 life cycle을 일치시키기
      • object : smart pointer
      • resource : heap memory, thread, file access, mutex...
    • memory leak 방지
  • smart pointer의 메모리 관리 예시
#include<iostream>
#include<memory>

class Cat
{
public:
	Cat()
		:mAge{0}
	{
		std::cout << "cat constructor" << std::endl;
	}
	~Cat() noexcept
	{
		std::cout << "cat desturctor" << std::endl;
	}
private:
	int mAge;
};

int main()
{	
	//raw pointer
	Cat* catRPtr = new Cat{ 3 };
	delete catRPtr;

	//smart pointer
    //delete가 필요없다.
	std::unique_ptr<Cat> catPtr = std::make_unique<Cat>(3);

	return 0;
}
  • scope base의 life cycle 예시
    • 출력결과 :
      before foo
      foo function
      cat constructor
      foo function end
      cat destructor
      after foo
    • std::vector 도 메모리 관리 스스로 해준다.
    • { } 안에서 생성을 하면 밖으로 나오면서 소멸된다.
#include<iostream>
#include<vector>
#include<memory>

class Cat
{
	//위의 Cat과 같은 구조
};

void foo()
{
	std::cout<< "foo function" <<std::endl;
    std::vector<Cat> cat(5);	
    std::cout<< "foo function end" <<std::endl;

}

int main()
{
	std::cout << "before foo" << std::endl;
	foo();
	std::cout << "after foo" << std::endl;
}

Unique Pointer

  • unique_ptr :
    • exclusive ownership
      -> 하나의 오브젝트를 하나의 포인터만이 가리킬 수 있는 것
      -> class에서 member variable로 pointer를 가져야 하는 경우에 자주 사용한다.
      ex) dynamic polymorphism
  • 예시 코드

    • main 함수에서 주석 처리된 부분은 raw pointer를 이용하는 부분
      -> 프로그램 작성 시 실수로 잘못된 동작을 하도록 만들 가능성이 높다.
      -> 주석 처리된 부분의 구조

    • smart pointer(unique_ptr) 이용
      -> 개발자가 소유권에 대한 생각을 하지 않아도 되기 때문에 쉽고 안전한 코드 작성 가능

#include<iostream>
#include<memory>

class Cat
{
public:
	Cat()
		:mAge{ 0 }
	{
		std::cout << "Cat constructor" << std::endl;
	}
	~Cat()
	{
		std::cout << "Cat desturctor" << std::endl;
	}

private:
	int mAge;
};

void foo(Cat*ptr)
{
	Cat* fooPtr = ptr;
}

int main()
{
	//Cat* catRPtr = new Cat();
	//Cat* catRPtr1 = catRPtr;
	//foo(catRPtr);
	//delete catRPtr;
	
	std::unique_ptr<Cat>catSPtr = std::make_unique<Cat>();
	//std::unique_ptr<Cat>catSPtr1 = catSPtr; 컴파일 오류-> 안정성 좋아짐
	std::unique_ptr<Cat>catSPtr2 = std::move(catSPtr); //ok -> ownership을 넘겨줌

	return 0;
}
  • 안정성 향상 코드 예시 (dynamic polymorphism)
    • rule of three :
      class에 포인터 member variable 있는 경우 개발자가 직접 destructor, copy/move constructor, copy/move assingment 만들어야 한다.
class Animal
{...};

class Tiger : public Animal
{...};

class Lion : public Animal
{...};

class Zoo
{
public:
	Zoo(int n)
	{
		if (n == 1)
			//mAnimal = new Tiger();
			mAnimal = std::make_unique<Tiger>(); //better
            -> rule of three 를 고려하지 않아도 된다
		else
			//mAnimal = new Lion();
			mAnimal = std::make_unique<Lion>(); //better
	}
private:
	//Animal* mAnimal;
	std::unique_ptr<Animal> mAnimal; //better
};

Shared Pointer

  • shared_ptr :
    -> shared ownership을 제공한다.
    -> reference counter를 가지고 메모리 leak을 방지한다.
  • shared_ptr을 잘 사용한 예시 코드
#include<iostream>
#include<memory>

class Cat
{
public:
	Cat()
		:mAge{ 0 }
	{
		std::cout << "Cat constructor" << std::endl;
	}
	~Cat()
	{
		std::cout << "Cat desturctor" << std::endl;
	}

private:
	int mAge;
};

class Dog
{
public:
	Dog()
	{
		std::cout << "Dog constructor" << std::endl;
	}
	~Dog()
	{
		std::cout << "Dog desturctor" << std::endl;
	}
};

int main()
{
	//raw pointer 
    //몇개의 포인터가 Cat 오브젝트를 가리키고 있는지 알 수가 없다. -> bad
	//Cat* catptr1 = new Cat();
	//Cat* catptr2 = catptr1;
	//delete catptr1;

	//shared_ptr 의 좋은 예시
	std::shared_ptr<Cat> catPtr1 = std::make_shared<Cat>();
	std::cout << "count: " << catPtr1.use_count() << std::endl; //1
	std::shared_ptr<Cat>catPtr2 = catPtr1;
	std::cout << "count: " << catPtr1.use_count() << std::endl; //2
}
  • shared_ptr 실수 예시
    • memory leak
      • 자기 자신을 가리키면서 reference count를 늘리는 경우
      • scope가 끝나서 포인터가 해제가 되지만 reference count는 2 -> 1이 되어서 memory leak이 발생한다.
    • circular reference 구조
class Dog
{
public:
	Dog()
	{
		std::cout << "Dog constructor" << std::endl;
	}
	~Dog()
	{
		std::cout << "Dog desturctor" << std::endl;
	}

	std::shared_ptr<Dog> mVar;
    std::shared_ptr<Dog> mFriend;
};

int main()
{
	//Shared pointer의 leak 예시
	std::shared_ptr<Dog>mPtr = std::make_shared <Dog>();
	mPtr->mVar = mPtr; //자기 자신을 가리킨다.
	std::cout << "count : " << mPtr.use_count() << std::endl; //2
    
    //자주하는 실수 예시
    std::shared_ptr<Dog> dogPtr1 = std::make_unique<Dog>();
	std::shared_ptr<Dog> dogPtr2 = std::make_unique<Dog>();

	dogPtr1->mFriend = dogPtr2;
	dogPtr2->mFriend = dogPtr1;
    //destructor가 호출 X, by circular reference -> memory leak
}

Weak Pointer

  • weak_ptr :
    -> shared_ptr과 동작 방식은 유사하지만 reference counter에 영향을 주지 않는다.
    -> weak_ptr을 사용하려면, shared_ptr로 변환하여 사용해야 한다.
    -> member variable로 shared_ptr을 둔 경우 circular reference 문제가 발생했는데, weak_ptr로 바꾸면 문제가 해결된다.
  • weak_ptr 예시 코드
#include<iostream>
#include<memory>

class Cat
{
public:
	Cat()
		:mAge{1}
	{
		std::cout << "Cat constructor" << std::endl;
	}
	~Cat()
	{
		std::cout << "Cat desturctor" << std::endl;
	}
	void speak()
	{
		std::cout << "Im cat.." << std::endl;
	}

private:
	int mAge;
};

int main()
{
	std::weak_ptr<Cat> wPtr; 
	{
		std::shared_ptr<Cat> sPtr = std::make_shared<Cat>();
		wPtr = sPtr;
		std::cout << "count: " << sPtr.use_count() << std::endl; //1

		if (const auto spt = wPtr.lock()) //weak pointer를 사용
		{
			std::cout << "count: " << sPtr.use_count() << std::endl; //2
			spt->speak();
		}
	}

	return 0;
}

Member Variable with Smart Pointer

  • unique_ptr
    • exclusive ownership을 위해 copy constructor를 삭제
      -> copy constructor가 필요하다면 만들어 주면 된다.
    • copy constructor 추가한 예시
class Dog
{
public:
	Dog(int n)
		:mVar{ std::make_unique<int>(n) }
	{
		std::cout << "constructor" << std::endl;
	}   
    //직접 만든 copy constructor 
    Dog(const Dog& other)
    	:mVar{ std::make_unique<int>(other.mVar) } 
    {}
	~Dog()
	{
		std::cout << "destructor" << std::endl;
	}
private:
	std::unique_ptr<int> mVar; 
};

int main()
{
	Dog dog1;
    //copy constructor 구현 이전에..
    //Dog dog2{dog1}; 컴파일 에러
    //dog3 = dog1; 컴파일 에러
}
  • shared_ptr
    • copy constructor를 사용해도 컴파일 에러는 발생하지 않는다.
      -> copy constructor를 통해 만든 오브젝트의 멤버 변수는 기존 오브젝트의 멤버 변수와 같은 곳을 가리키게 된다.

      -> 해당 방식은 class 설계자의 의도일수도 있으므로 class에 명시해주는 것이 좋다.
      -> deep copy interface를 구현해서 혼동하지 않게 도와준다.
    • shared_ptr을 이용해서 잘 만든 코드 예시
class Dog
{
public:
	explicit Dog(int n)
		:mVar{ std::make_shared<int>(n) }
	{
		std::cout << "constructor" << std::endl;
	}
	~Dog() noexcept
	{
		std::cout << "destructor" << std::endl;
	}
	Dog clone() const //deep copy interface
	{
		Dog temp{ *mVar };
		return temp;
	}
private:
	std::shared_ptr<int> mVar;
};

int main()
{
	const Dog dog1{ 2 };
    
    //의도에 맞게 사용
    const Dog dog2{dog1}; 
	const Dog dog3{ dog1.clone() }; //deep copy
}

0개의 댓글