26: 변수 정의는 늦출 수 있는 데까지 늦추는 근성을 발휘하자
생성자 혹은 소멸자를 호출하는 타입으로 변수를 정의하면 반드시 물게 되는 비용이 두 개있다. 하나는 생성자가 호출되는 비용이고, 하나는 소멸자가 호출되는 비용이다. 변수가 정의됐으나 사용되지 않은 경우에도 비용이 부과되는데, 이런 비용은 웬만한 경우가 아니면 물고 싶을 생각이 안 들 것이다.
함수 실행도중 예외를 던지는 코드이다.
string encryptPassword(const string& password)
{
string encrypted;
// 최소 길이보다 비밀번호가 짧으면 예외를 던진다.
if (password.length() < MinimunPasswordLength)
{
throw logic_error("Password is too shrot");
}
// 비밀번호를 암호화하여 encrypted에 넣는 곳
return encrypted;
}
여기서 encrypted 객체가 완전히 안 쓰이진 않지만 중간에 예외를 던지더라도 encrypted 객체의 생성과 소멸에 대해 비용을 내야 한다. 그러므로 encrypted 변수를 정의하는 일은 꼭 필요해지기 전까지 미루는 편이 좋다.
string encryptPassword(const string& password)
{
// 최소 길이보다 비밀번호가 짧으면 예외를 던진다.
if (password.length() < MinimunPasswordLength)
{
throw logic_error("Password is too shrot");
}
string encrypted;
// 비밀번호를 암호화하여 encrypted에 넣는 곳
return encrypted;
}
여기서 객체를 생성할 때, 기본 생성하고 나서 값을 대입하는 방법은 원하는 값으로 직접 초기화하는 방법보다 비효율적이다(항목 4). 다음의 함수에서 .encryptedPassword의 암호화 부분이 돌아간다고 가정해 보자.
void encrypt(string& s);
바람직한 방법은 encrpyted를 password로 바로 초기화해 버려야 한다. 요컨대, 의미도 없고 비용도 만만치 않을 듯한 생성자 호출을 건너뛰어야 한다는 이야기다.
string encryptPassword(const string& password)
{
// 최소 길이보다 비밀번호가 짧으면 예외를 던진다.
if (password.length() < MinimunPasswordLength)
{
throw logic_error("Password is too shrot");
}
string encrypted(password);
encrypt(encrypted);
return encrypted;
}
변수를 사용해야 할 때가 오기 전까지 그 변수의 정의를 늦추는 것은 기본이고, 초기화 인자를 손에 넣기 전까지 정의를 늦출 수 있는지도 둘러봐야 한다는 것이다. 이러면 변수의 쓰임새를 문서화하는 데도 큰 도움이 된다.
그럼 루프에서는 어떨까?
// 방법 A: 루프 바깥쪽에 정의
Widget w;
for (int i = 0; i < n; ++i)
{
w = i에 따라 달라지는 값;
}
// 방법 B: 루프 안쪽에 정의
for (int i = 0; i < n; ++i)
{
Widget w(i에 따라 달라지는 값);
}
두 방법에 대해 걸리는 비용을 정리해 보면 다음과 같다.
- A 방법: 생성자 1번 + 소멸자 1번 + 대입 n번
- B 방법: 생성자 n번 + 소멸자 n번
대입에 들어가는 비용이 생성자/소멸자 쌍보다 적게 나오는 경우가 있는데 이런 종류에 속한다면 A 방법이 일반적으로 훨씬 효율이 좋다. 대신, w라는 이름을 볼 수 있는 유효범위가 B 방법을 쓸 때보다 넓어지기 때문에(루프를 포함하는 유효범위), 프로그램의 이해도와 유지보수성이 역으로 안 좋아질 수도 있다.
대입이 생성자/소멸자 쌍보다 비용이 덜 들고, 전체 코드에서 수행 성능에 민감한 부분을 건드리는 중이라고 생각하지 않는다면, 앞뒤 볼 것 없이 B 방법으로 가는 것이 좋다.
이것만은 잊지 말자!
- 변수 정의는 늦출 수 있을 때까지 늦추자. 프로그램이 더 깔끔해지며 효율도 좋아진다.
'C++ > Effective C++' 카테고리의 다른 글
[Effective C++] 28: 내부에서 사용하는 객체에 대한 '핸들'을 반환하는 코드는 되도록 피하자 (0) | 2023.10.26 |
---|---|
[Effective C++] 27: 캐스팅은 절약, 또 절약! 잊지 말자 (0) | 2023.10.26 |
[Effective C++] 25: 예외를 던지는 swap에 대한 지원도 생각해 보자 (1) | 2023.10.22 |
[Effective C++] 24: 타입 변환이 모든 매개변수에 대해 적용되어야 한다면 비멤버 함수를 선언하자 (1) | 2023.10.21 |
[Effective C++] 23: 멤버 함수보다는 비멤버 비프렌드 함수와 더 가까워지자 (1) | 2023.10.20 |