C++/Effective C++

[Effective C++] 26: 변수 정의는 늦출 수 있는 데까지 늦추는 근성을 발휘하자

KANTAM 2023. 10. 23. 22:52

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 방법으로 가는 것이 좋다.

 

이것만은 잊지 말자!

  • 변수 정의는 늦출 수 있을 때까지 늦추자. 프로그램이 더 깔끔해지며 효율도 좋아진다.