- 변수 정의는 늦출 수 있을 때까지 늦춰라. 프로그램도 깔끔해지고 효율도 좋아진다.
변수 정의는 최대한 늦게
변수를 정의할 때 반드시 들어가는 비용이 있다. 바로 생성자와 소멸자를 호출하는 비용이다.
가끔 사용하지도 않는 변수에 대해 비용이 드는 경우도 있다.
std::string encryptPassword(const std::string& password)
{
using namespace std;
string encryted;
if(password.length() < MinimumPasswordLength) {
throw logic_error("Password is too short");
}
... // 암호화
return encryted;
}
encryted 객체는 password의 길이가 짧아 예외가 발생하게 되면 사용되지 않는다.
하지만, 사용하지 않더라도 생성, 소멸 비용이 드는 것이다.
이런 상황을 방지하기 위해 변수를 최대한 늦게 정의하는 것이 좋다.
std::string encryptPassword(const std::string& password)
{
using namespace std;
if(password.length() < MinimumPasswordLength) {
throw logic_error("Password is too short");
}
string encryted;
... // 암호화
return encryted;
}
한 가지 더 최적화할 수 있는 부분이 있다.
std::string encryptPassword(const std::string& password)
{
... // 길이 점검
string encryted;
encryted = password;
... // 암호화
return encryted;
}
지금은 encryted 객체를 생성한 후 대입하고 있다.
비용을 따져보면 생성자 1번, 대입 1번의 비용이 든다.
이때, encryted 객체의 생성자를 통해 초기화를 진행한다면 대입연산에 드는 비용을 아낄 수 있다.
std::string encryptPassword(const std::string& password)
{
... // 길이 점검
string encryted(password);
... // 암호화
return encryted;
}
정리하자면, 어떤 변수를 사용해야 할 때가 오기 전까지 그 변수의 정의를 늦추는 것은 기본이고, 초기화 인자를 획득하기 전까지 정의를 늦출 수 있는지 확인해야 한다.
loop에서
loop안에서 사용하는 변수에 대해서 보면 두 가지 경우가 있다.
//Type A
Widget w;
for(int i = 0; i < n; ++i) {
w = i;
...
}
//Type B
for(int i = 0; i < n; ++i) {
Widget w(i);
...
}
Type A는 loop 밖에 변수를 정의하고 loop 안에서 대입하며 처리한다.
Type B는 loop안에서 변수를 정의하며 처리한다.
두 방법에 걸리는 비용을 계산해 보면 다음과 같다.
- Type A: 생성자 1번 + 소멸자 1번 + 대입 n번
- Type B: 생성자 n번 + 소멸자 n번
객체의 생성자, 소멸자, 대입 연산에 따라 얘기가 달라질 수 있다.
만약, 대입에 들어가는 비용이 생성자, 소멸자 쌍보다 적게 나오면 Type A가 좋을 것이다. (대입 < 생성, 소멸)
그렇지 않다면, Type B가 좋다. (대입 >= 생성, 소멸)
또한, Type A에서는 w라는 변수에 대한 유효범위가 넓어지기 때문에 유지보수성이 안 좋아질 수 있다.
정리하자면, 대입이 생성자-소멸자 쌍보다 비용이 덜 들고 전체 코드에서 수행 성능이 중요하다면 무조건 Type B를 사용해라.