소멸자(Destructor)소멸자는 개체를 파괴할 때마다 호출되는 함수이다.
~complex(){
std::cout<<"So long and thanks for the finish.\n";
}
소멸자는 디폴트 생성자의 보수 연산이므로 보수를 나타내는 표기법(~)를 사용한다.
소멸자는 생성자와 달리 하나의 오버로드만 있고 인수를 허용하지 않는다.
구현 규칙1. 소멸자에서 예외를 던지지 말아라! 프로그램 크래시가 발생하고 예외를 전혀 발생하지 못할 가능성이 높다.
C++11 이상부터는 항상 런타임 오류로 처리됨
2. 클래스에 virtual 함수가 포함되어 잇으면, 소멸자도 virtual 이어야 한다.
리소스 처리소멸자의 주된 임무는 개체의 리소스(메모리, 파일 핸들, 소켓, 락, …)를 해제하고 더 이상 필요하지 않은 개체와
관련된 모든 것을 정리하는 일이다.
소멸자가 예외를 던지면 안되기 때문에 리소스를 해제하는 일만 소멸자에게 할당하는 것이 좋다
class vector{
public:
~vector(){
delete[] data;
}
private:
unsigned my_size;
double* data;
};
delete는 포인터가 nullptr(C++03에서는 0)인지 검사한다.
리소스 획득은 초기화(Resource Acquisition Is Initialization)리소스를 개체이 묶고 개체 생성 및 파괴 메커니즘을 사용해 프로그램에서 리소스를 자동으로 처리
리소스를 얻으려 할 때마다 리소스를 소유하는 개체를 생성해서 리소스를 얻는다.
개체가 범위를 벗어날 때마다 리소스(메모리, 파일, 소켓, … )를 자동으로 해제한다.
C++는 이미 표준 라이브러리에서 리소스를 관리하는 클래스를 제공한다.
파일 스트림은 C에서 파일 핸들을 관리한다.
unique_ptr, shared_ptr는 메모리 누수 방지, 예외에 안전한 방식으로 처리한다.
리소스 관리자를 구현할 때 ,한 클래스에 하나 이상의 리소스를 관리해서는 안된다.
(예외가 발생할 수 있음, 모든 리소스의 해제를 보장하는 방식으로 소멸자를 구현하기 어려움)
두 리소스를 처리하는 클래스를 작성할 때마다 리소스 중 하나를 관리하는 클래스를 도입해야 한다.
두 리소스의 관리자를 작성하고, 리소스의 내용과 처리 로직을 완전히 분리해야 한다.
단일 책임 원칙(Single Responsibility Principle, SRP)
하나의 단일 개체는 리소스에 대한 책임이 있으며 수명이 끝날 때, 리소스를 해제해야 함
리소스 구조예제는 C++에서 Oracle 데이터베이스에 접근하기 위해 Oracle C++ Call Interface(OCCI)로 시연
OCCI는 C 라이브러리 OCI의 C++ 확장이며, 전체 소프트웨어 아키텍처를 C 스타일로 유지하면서
일부 C++ 기능이 포함된 얇은 레이어만 추가한다.
(C는 소멸자를 사용할 수 없기 때문에, 명시적으로 리소스를 해제해야 함)
#include <iostream>
#include <string>
#include <occi.h>
using namespace std;
using namespace oracle::occi;
int main(void){
string dbConn="172.17.42.1", user="Herbert", password="NSA_go_away";
Environment* env=Environment::createEnvironment();
Connection* conn=env->createConnection(user, password, dbConn);
string query="select problem from my_solutions where awrad_worthy!=0";
Statement* stmt=conn->createStatement(query);
ResultSet* rs=stmt->executeQuery();
while(rs->next()) cout<<rs->getString(1)<<endl;
stmt->closeResultSet(rs);
conn->terminateStatement(stmt);
env->terminateConnection(conn);
Environment::terminateEnvironment(env);
}
위의 코드와 같이 일체식 블록을 사용할 때, 리소스 해제 작업은 생성한 역순으로 리소스를 해제해 주면된다.
ResultSet* rs=makes_me_famous();
while(rs->next()) cout<<rs->getString(1)<<endl;
ResultSet* rs1=needs_more_word();
while(rs2->next()) cout<<rs2->getString(1)<<endl;
만일 쿼리를 이용해서 함수를 구축한 경우,
닫는 작업에 대해 대응하는 Statement가 없는 ResultSet를 갖는다.
(ResultSet을 makes_me_famous(), needs_more_word() 내에서 선언함, 범위를 벗어났음)
모든 개체 중 생성을 위해 사용한 개체를 추가로 유지해야 한다.
위처럼 “다른 리소스에 의존하는 리소스”를 관리하기 위해서
unique_ptr, shared_ptr의 삭제자를 활용하는 것이 좋다.
삭제자는 관리되는 메모리가 해제될 때마다 호출된다.
struct environment_deleter{
void operator()(Environment* env){
Environment::terminateEnvironment(env);
}
};
shared_ptr<Environment> environment(
Environment:: createEnvironment(), environment_deleter{});
Environment는 다른 리소스에 의존하지 않기 때문에 처리하기 가장 수월함
environment는 마지막 복사본이 범위를 벗어날 때, terminateEnvironment(env)를 실행해
삭제자가 호출되도록 보장한다.
종료될 때, environment_deleter(environment) 수행<-()operator overloading
struct connection_deleter{
connection_deleter(shared_ptr<Environment> env): env(env) {}
void operator()(Connection* conn){
env->terminateConnection(conn);
}
shared_ptr<Environment> env;
};
shared_ptr<Connection> connection(envrionment->createConnection(...),
connection_deleter{environment});
Connection은 생성 및 종료를 위한 Environment를 필요로 한다.
(connection_deleter struct에 복사본을 저장)
더 이상 필요하지 않을 때, Connection이 종료됨이 보장된다.
connection_deleter에 Environment 복사본이 저장되어 있기 때문에 Connection이 존재하는 한
종료되지 않는다.
종료될 때, connection_deleter(connection) 수행<-()operator overloading
class db_managr{
public:
using ResultSetSharedPtr=std::shared_ptr<ResultSet>;
db_manager(string const& dbConnection, string const& dbUser, string const& dbPw): environment(Environment::createEnvironment(), environment_deleter{}), connection(environment->createConnection(dbUser, dbPw, dbConnection), connection_deleter{environment}) {}
private:
shared_ptr<Environment> environment;
shared_ptr<Connection> connection;
};
멤버들이 리소스를 관리하므로, 클래스는 소멸자를 별도로 필요로 하지 않는다.
ResultSet을 반환한는 쿼리 메서드
struct result_set_deleter{
result_set_deleter(shared_ptr<Connection> conn, Statement* stmt)
:conn(conn), stmt(stmt){}
void operator()(ResultSet *rs){
stmt->closeResultSet(rs);
conn->terminateStaement(stmt);
}
shared_ptr<Connection> conn;
Statement* stmt;
};
class db_manager{
public:
ResultSetSharedPtr query(const std::string& q) const{
statement* stmt=connection->createStatement(q);
ResultSet* rs=stmt->executeQuery();
auto deleter=result_set_deleter{connection, stmt};
return ResultSetSharedPtr{rs, deleter};
}
};
int main(void){
db_manager db("172.17.42.1", "Herbert", "NSA_go_away");
auto rs=db.query("select problem from my_solution where award_worthy!=0");
while(rs->next()) cout<<rs->getString(1)<<endl;
}