diff --git a/sections/appendix/DIscussion.md b/sections/appendix/DIscussion.md index fbfe0a2..783dc94 100644 --- a/sections/appendix/DIscussion.md +++ b/sections/appendix/DIscussion.md @@ -1,12 +1,13 @@ # Appendix C: Discussion -This section contains follow-up material on rules and sets of rules. -In particular, here we present further rationale, longer examples, and discussions of alternatives. +이 절은 규칙들과 규칙들의 집합에 대한 추가적인 내용을 담고 있다. +특히, 여기서 추가적인 이유, 더 긴 예제, 대안책들에 대한 토론들을 표현하고자 한다. -### Discussion: Define and initialize member variables in the order of member declaration +### 토론: 멤버 변수들을 멤버 선언 순서에 따라 정의하고 초기화하라 -Member variables are always initialized in the order they are declared in the class definition, so write them in that order in the constructor initialization list. Writing them in a different order just makes the code confusing because it won't run in the order you see, and that can make it hard to see order-dependent bugs. +멤버 변수들은 항상 클래스의 정의부에서 선언된 순서에 따라 초기화되므로, 생성자의 초기화리스트에 그 순서대로 작성해야 한다. +선언과 다른 순서로 작성하는 것은 의미를 알 수 없는 코드를 만드는데, 멤버 변수들이 눈에 보이는 순서대로 실행되지 않아 순서에 종속적인 버그를 찾기 어렵기 때문이다. ```c++ class Employee { @@ -19,113 +20,128 @@ Member variables are always initialized in the order they are declared in the cl Employee::Employee(const char* firstName, const char* lastName) : first(firstName), last(lastName), - // BAD: first and last not yet constructed + // BAD: first 와 last 가 어직 생성되지 않음 email(first + "." + last + "@acme.com") {} ``` -In this example, `email` will be constructed before `first` and `last` because it is declared first. That means its constructor will attempt to use `first` and `last` too soon -- not just before they are set to the desired values, but before they are constructed at all. +이 예제에서 `email`은 맨 처음에 선언되었기 때문에 `first`와 `last` 보다 먼저 생성된다. 그 말은 `first`와 `last`가 원하는 값으로 설정되기 전이 아니라 아예 생성되기도 전에 `email`의 생성자가 사용하려 한다는 뜻이다. -If the class definition and the constructor body are in separate files, the long-distance influence that the order of member variable declarations has over the constructor's correctness will be even harder to spot. +클래스의 정의부와 생성자 본문이 서로 다른 파일에 있다면, 멤버 변수의 선언 순서가 생성자의 정확성에 미치는 장거리 영향력을 파악하기 훨씬 더 어려워진다. **References**: -* [\[Cline99\]](#Cline99) §22.03-11 -* [\[Dewhurst03\]](#Dewhurst03) §52-53 -* [\[Koenig97\]](#Koenig97) §4 -* [\[Lakos96\]](#Lakos96) §10.3.5 -* [\[Meyers97\]](#Meyers97) §13 -* [\[Murray93\]](#Murray93) §2.1.3 -* [\[Sutter00\]](#Sutter00) §47 +* [\[Cline99\]](../Bibliography.md) §22.03-11 +* [\[Dewhurst03\]](../Bibliography.md) §52-53 +* [\[Koenig97\]](../Bibliography.md) §4 +* [\[Lakos96\]](../Bibliography.md) §10.3.5 +* [\[Meyers97\]](../Bibliography.md) §13 +* [\[Murray93\]](../Bibliography.md) §2.1.3 +* [\[Sutter00\]](../Bibliography.md) §47 -### Discussion: Use of `=`, `{}`, and `()` as initializers +### 토론: 초기화로 `=`, `{}`, `()` 를 사용하라 ??? -### Discussion: Use a factory function if you need "virtual behavior" during initialization +### 토론: 초기화 중에 "가상 동작(virtual behavior)"이 필요한 경우 팩토리(factory) 함수를 사용하라 -If your design wants virtual dispatch into a derived class from a base class constructor or destructor for functions like `f` and `g`, you need other techniques, such as a post-constructor -- a separate member function the caller must invoke to complete initialization, which can safely call `f` and `g` because in member functions virtual calls behave normally. Some techniques for this are shown in the References. Here's a non-exhaustive list of options: +`f`와 `g` 같은 함수에 대한 기반 클래스 생성자 또는 소멸자로부터 파생 클래스로 가상 디스패치를 원하는 경우, 다른 기법이 필요하다. 예를 들어 포스트 생성자 기법은 호출자가 초기화를 완료하기 위해 호출해야 하는 별도의 멤버 함수로, 멤버 함수에서는 가상 호출이 정상적으로 작동하므로 `f`와 `g`를 안전하게 호출할 수 있다. 이외에도 몇 가지 기법이 참고 문헌에 나와있다. 다음은 전체가 아닌 옵션 목록이다: -* *Pass the buck:* Just document that user code must call the post-initialization function right after constructing an object. -* *Post-initialize lazily:* Do it during the first call of a member function. A Boolean flag in the base class tells whether or not post-construction has taken place yet. -* *Use virtual base class semantics:* Language rules dictate that the constructor most-derived class decides which base constructor will be invoked; you can use that to your advantage. (See [\[Taligent94\]](#Taligent94).) -* *Use a factory function:* This way, you can easily force a mandatory invocation of a post-constructor function. +* *책임 전가(Pass the buck):* 사용자 코드가 객체를 생성한 직후에 초기화이 후 함수를 호출해야 한다는 점을 문서화하라 +* *느린 초기화 후(Post-initialize lazily):* 멤버 함수를 처음 호출하는 동안 수행하라. 기반 클래스의 부울 플래그를 통해 객체 생성 후 초기화가 이루어지지 않았는지 여부를 확인하라 +* *가상 기반 클래스 시멘틱 사용(Use virtual base class semantics):* 언어 규칙에 따라 가장 많이 파생된 클래스가 어떤 기본 생성자를 호출할지 결정하므로 이를 유리하게 사용할 수 있다. ([\[Taligent94\]](../Bibliography.md) 참고) +* *팩토리 함수를 사용하라:* 이렇게 하면 생성자 이후 함수의 필수 호출을 쉽게 강제할 수 있다. -Here is an example of the last option: +다음은 마지막 옵션의 예시 입니다: ```c++ class B { public: - B() { /* ... */ f(); /* ... */ } // BAD: see Item 49.1 + B() + { + /* ... */ + f(); // BAD: C.82: 생성자 및 소멸자에서 가상 함수를 호출하지 마라 + /* ... */ + } virtual void f() = 0; - - // ... }; class B { protected: - B() { /* ... */ } - virtual void post_initialize() // called right after construction - { /* ... */ f(); /* ... */ } // GOOD: virtual dispatch is safe + class Token {}; + public: + // 생성자는 make_shared가 접근할 수 있도록 public이어야 한다. + // protected 접근 수준은 Token을 요구하여 얻을 수 있다. + explicit B(Token) { /* ... */ } // 불완전하게 초기화된 객체 생성 virtual void f() = 0; template - static shared_ptr create() // interface for creating objects + static shared_ptr create() // 객체 생성을 위한 인터페이스 { - auto p = make_shared(); + auto p = make_shared(typename T::Token{}); p->post_initialize(); return p; } + + protected: + virtual void post_initialize() // 생성 직후 호출 + { /* ... */ f(); /* ... */ } // GOOD: 가상 디스패치는 안전합니다 }; - class D : public B { // some derived class + class D : public B { // 일부 파생 클래스 + protected: + class Token {}; + public: + // 생성자는 make_shared가 접근할 수 있도록 public이어야 한다. + // protected 접근 수준은 Token을 요구하여 얻을 수 있다. + explicit D(Token) : B{ B::Token{} } {} void f() override { /* ... */ }; protected: - D() {} - template - friend shared_ptr B::Create(); + friend shared_ptr B::create(); }; - shared_ptr p = D::Create(); // creating a D object + shared_ptr p = D::create(); // D 객체 생성 ``` -This design requires the following discipline: +이 설계에는 다음과 같은 원칙이 필요하다: -* Derived classes such as `D` must not expose a public constructor. Otherwise, `D`'s users could create `D` objects that don't invoke `PostInitialize`. -* Allocation is limited to `operator new`. `B` can, however, override `new` (see Items 45 and 46). -* `D` must define a constructor with the same parameters that `B` selected. Defining several overloads of `Create` can assuage this problem, however; and the overloads can even be templated on the argument types. +* `D`와 같은 파생 클래스는 공개적으로 호출 가능한 생성자를 노출해서는 안된다. 그렇지 않으면 `D`의 사용자가 `초기화 후 함수(PostInitialize)`를 호출하지 않은 `D`객체를 생성할 수 있다. +* 할당은 `operator new`로 제한된다. 그러나 `B`는 `new`를 재정의할 수 있다([SuttAlex05](../Bibliography.md)의 Items 45 and 46 참조). +* `D`는 `B`가 선택한 것과 동일한 매개변수를 가진 생성자를 정의해야만 한다. 그러나 `create`의 여러 오버로드를 정의하면서 이 문제를 완화할 수 있으며, 오버로드는 인수 유형에 템플릿을 지정할 수도 있다. -If the requirements above are met, the design guarantees that `PostInitialize` has been called for any fully constructed `B`-derived object. `PostInitialize` doesn't need to be virtual; it can, however, invoke virtual functions freely. +위의 요구사항이 충족되면, 설계는 완전히 생성된 모든 `B` 파생 객체에 대해 `PostInitialize`가 호출되었음을 보장한다. `PostInitialize`는 가상일 필요는 없지만 가상 함수를 자유롭게 호출할 수 있어야 한다. -In summary, no post-construction technique is perfect. The worst techniques dodge the whole issue by simply asking the caller to invoke the post-constructor manually. Even the best require a different syntax for constructing objects (easy to check at compile time) and/or cooperation from derived class authors (impossible to check at compile time). +요약하면, 완벽한 생성 후 기법은 존재하지 않는다. 최악의 기법은 호출자에게 생성자 이후 수동으로 호출하도록 요청함으로써 모든 문제를 회피하는 것이다. 가장 좋은 방법도 객체를 구성하는 다른 구문(컴파일 때 쉽게 확인 가능)이나 파생 클래스 작성자의 협조(컴파일 때 확인 불가능)가 필요하다. **References**: -* [\[Alexandrescu01\]](#Alexandrescu01) §3 -* [\[Boost\]](#Boost) -* [\[Dewhurst03\]](#Dewhurst03) §75 -* [\[Meyers97\]](#Meyers97) §46 -* [\[Stroustrup00\]](#Stroustrup00) §15.4.3 -* [\[Taligent94\]](#Taligent94) +* [\[Alexandrescu01\]](../Bibliography.md) §3 +* [\[Boost\]](../Bibliography.md) +* [\[Dewhurst03\]](../Bibliography.md) §75 +* [\[Meyers97\]](../Bibliography.md) §46 +* [\[Stroustrup00\]](../Bibliography.md) §15.4.3 +* [\[Taligent94\]](../Bibliography.md) -### Discussion: Make base class destructors public and virtual, or protected and nonvirtual +### 토론: 기반 클래스 소멸자를 공개, 가상 또는 보호 및 비가상으로 설정하라 -Should destruction behave virtually? That is, should destruction through a pointer to a `base` class be allowed? If yes, then `base`'s destructor must be public in order to be callable, and virtual otherwise calling it results in undefined behavior. Otherwise, it should be protected so that only derived classes can invoke it in their own destructors, and nonvirtual since it doesn't need to behave virtually virtual. +소멸이 가상으로 작동해야만 하는가? 즉, `base` 클래스에 대한 포인터를 통한 소멸이 허용되어야 하는가? +위 질문에 대한 대답이 '그렇다' 라면, `base` 의 소멸자는 호출 가능해야 하고 그렇지 않으면 가상으로 호출하면 정의되지 않은 동작이 발생한다. +대답이 '아니오' 라면, 파생 클래스만 자체 소멸자에서 호출할 수 있도록 보호해야 하며, 가상으로 동작할 필요가 없으므로 비가상이어야 한다. -##### Example +##### 예제 -The common case for a base class is that it's intended to have publicly derived classes, and so calling code is just about sure to use something like a `shared_ptr`: +기반 클래스는 보통 공개적으로 파생된 클래스를 갖기 위한 것이므로 코드를 호출할 때 `shared_ptr`와 같은 것을 사용해야 한다: ```c++ class Base { public: - ~Base(); // BAD, not virtual + ~Base(); // BAD, 비가상 virtual ~Base(); // GOOD // ... }; @@ -135,173 +151,174 @@ The common case for a base class is that it's intended to have publicly derived { unique_ptr pb = make_unique(); // ... - } // ~pb invokes correct destructor only when ~Base is virtual + } // ~Base 가 가상인 경우에만 ~pb가 올바른 소멸자를 호출 ``` -In rarer cases, such as policy classes, the class is used as a base class for convenience, not for polymorphic behavior. It is recommended to make those destructors protected and nonvirtual: +단위 전략(policy) 클래스와 같이 드문 경우에 다형성 동작이 아닌 편의상 기반 클래스로 사용된다. 이러한 소멸자는 protected와 비가상으로 만드는 것이 좋다: ```c++ class My_policy { public: - virtual ~My_policy(); // BAD, public and virtual + virtual ~My_policy(); // BAD, public 과 virtual protected: ~My_policy(); // GOOD // ... }; template - class customizable : Policy { /* ... */ }; // note: private inheritance + class customizable : Policy { /* ... */ }; // note: private 상속 ``` ##### Note -This simple guideline illustrates a subtle issue and reflects modern uses of inheritance and object-oriented design principles. +이 간단한 가이드라인은 미묘한 문제를 설명하며 상속 및 객체 지향 설계 원칙의 현대적인 사용을 반영한다. -For a base class `Base`, calling code might try to destroy derived objects through pointers to `Base`, such as when using a `unique_ptr`. If `Base`'s destructor is public and nonvirtual (the default), it can be accidentally called on a pointer that actually points to a derived object, in which case the behavior of the attempted deletion is undefined. This state of affairs has led older coding standards to impose a blanket requirement that all base class destructors must be virtual. This is overkill (even if it is the common case); instead, the rule should be to make base class destructors virtual if and only if they are public. +기반 클래스 `Base`의 경우, `unique_ptr`를 사용할 때와 같이 호출 코드가 `Base`에 대한 포인터를 통해 파생 객체를 삭제하려 할 수 있다. `Base`의 소멸자가 public이고 비가상인 경우(기본값), 실제로 파생 객체를 가리키는 포인터에서 실수로 호출될 수 있으며, 이 경우 삭제 시도의 동작이 정의되지 않는다. 이러한 상황으로 인해 이전 코딩 표준에서는 모든 기반 클래스 소멸자가 가상이어야 한다는 포괄적인 요건을 부과했다. 이것은 (일반적인 경우라고 해도) 지나친 요구다. 대신 기반 클래스 소멸자가 public인 경우에만 가상으로 만드는 것으로 규칙을 만들어야 한다. -To write a base class is to define an abstraction (see Items 35 through 37). Recall that for each member function participating in that abstraction, you need to decide: +기반 클래스를 만드는 것은 추상화를 정의하는 것이다(Items 35 ~ 37 참조). 해당 추상화에 참여하는 각 멤버 함수에 대해 결정해야 한다는 점을 상기하라: -* Whether it should behave virtually or not. -* Whether it should be publicly available to all callers using a pointer to `Base` or else be a hidden internal implementation detail. +* 가상으로 동작해야 하는지 여부. +* `Base`에 대한 포인터를 사용하여 모든 호출자가 공개적으로 사용할 수 있어야 하는지 아니면 숨겨진 내부 구현 세부 사항이어야 하는지 여부 -As described in Item 39, for a normal member function, the choice is between allowing it to be called via a pointer to `Base` nonvirtually (but possibly with virtual behavior if it invokes virtual functions, such as in the NVI or Template Method patterns), virtually, or not at all. The NVI pattern is a technique to avoid public virtual functions. +Item 39에 설명된 대로, 일반 멤버 함수의 경우, `Base`에 대한 포인터를 통해 호출할 수 있도록 허용할지, 가상 함수를 호출하는 경우 가상 동작을 포함할지(NVI 또는 템플릿 메서드 패턴과 같이 가상 함수를 호출하는 경우), 가상으로 호출할지 또는 전혀 호출하지 않을지 선택할 수 있다. NVI 패턴은 public 가상 함수를 피하기 위한 기법이다.(NVI : Non-virtual-interface) -Destruction can be viewed as just another operation, albeit with special semantics that make nonvirtual calls dangerous or wrong. For a base class destructor, therefore, the choice is between allowing it to be called via a pointer to `Base` virtually or not at all; "nonvirtually" is not an option. Hence, a base class destructor is virtual if it can be called (i.e., is public), and nonvirtual otherwise. +소멸은 비가상 호출을 위험하거나 잘못되게 만드는 특별한 시맨틱(semantics)이 있긴 하지만, 또 다른 연산으로 볼 수 있다. 그러므로 기반 클래스 소멸자의 경우, `Base`에 대한 포인터를 통해 가상으로 호출할 수 있는지 아니면 전혀 호출할 수 없는지를 선택해야 하며, "비가상"은 옵션이 아니다. 이런 이유로 기반 클래스 소멸자는 호출할 수 있는 경우(즉, public인 경우) 가상이고, 그렇지 않은 경우 비가상이다. -Note that the NVI pattern cannot be applied to the destructor because constructors and destructors cannot make deep virtual calls. (See Items 39 and 55.) +생성자와 소멸자는 심층 가상 호출을 할 수 없으므로 소멸자에는 NVI 패턴을 적용할 수 없다.(Items 39 및 55 참조.) -Corollary: When writing a base class, always write a destructor explicitly, because the implicitly generated one is public and nonvirtual. You can always `=default` the implementation if the default body is fine and you're just writing the function to give it the proper visibility and virtuality. +결론: 기반 클래스를 작성할 때는 항상 소멸자를 명시적으로 작성하라. 암시적으로 생성된 소멸자는 public이고 비가상이기 떄문이다. 기본 형태가 괜찮고 적절한 가시성과 가상성을 제공하는 함수를 작성하는 경우 언제든지 구현을 `=default`로 설정할 수 있다. -##### Exception +##### 예외 -Some component architectures (e.g., COM and CORBA) don't use a standard deletion mechanism, and foster different protocols for object disposal. Follow the local patterns and idioms, and adapt this guideline as appropriate. +일부 컴포넌트 아키텍처(예: COM 및 CORBA)는 표준 삭제 메커니즘을 사용하지 않으며, 객체 처리를 위해 다른 프로토콜을 장려한다. 로컬 패턴(local pattern)과 관용구를 따르고 이 가이드라인을 적절히 적용하라. -Consider also this rare case: +또한 다음과 같이 드문 경우들도 고려하라: -* `B` is both a base class and a concrete class that can be instantiated by itself, and so the destructor must be public for `B` objects to be created and destroyed. -* Yet `B` also has no virtual functions and is not meant to be used polymorphically, and so although the destructor is public it does not need to be virtual. +* `B`는 기반 클래스인 동시에 그 자체로 인스턴스화할 수 있는 구체적인 클래스이므로 `B` 객체를 생성하고 소멸하려면 소멸자가 public이어야 한다. -Then, even though the destructor has to be public, there can be great pressure to not make it virtual because as the first virtual function it would incur all the run-time type overhead when the added functionality should never be needed. +소멸자가 public이 되어야 하더라도, 첫 번째 가상 함수로서 추가 기능이 필요하지 않은데도 모든 런타임 유형 오버헤드가 발생하기 때문에 가상으로 만들지 않는 것이 큰 부담이 될 수 있다. -In this rare case, you could make the destructor public and nonvirtual but clearly document that further-derived objects must not be used polymorphically as `B`'s. This is what was done with `std::unary_function`. +드문 경우지만, 소멸자를 public이고 비가상으로 만들되, 추가 파생 객체를 `B`처럼 다형성으로 사용해서는 안 된다는 점을 명확하게 문서화할 수 있다. 이것이 `std::unary_function`으로 수행한 작업이다. -In general, however, avoid concrete base classes (see Item 35). For example, `unary_function` is a bundle-of-typedefs that was never intended to be instantiated standalone. It really makes no sense to give it a public destructor; a better design would be to follow this Item's advice and give it a protected nonvirtual destructor. +그러나 일반적으로 구체적인 기반 클래스는 피하라(Item 35 참조). 예를 들어, `unary_function`은 독립적으로 인스턴스화되도록 의도되지 않은 타입 정의 번들이다. 이 항목의 조언을 따르고 protected, 비가상 소멸자를 제공하는 것이 더 나은 설계이다. **References**: -* [\[C++CS\]](#CplusplusCS) Item 50 -* [\[Cargill92\]](#Cargill92) pp. 77-79, 207 -* [\[Cline99\]](#Cline99) §21.06, 21.12-13 -* [\[Henricson97\]](#Henricson97) pp. 110-114 -* [\[Koenig97\]](#Koenig97) Chapters 4, 11 -* [\[Meyers97\]](#Meyers97) §14 -* [\[Stroustrup00\]](#Stroustrup00) §12.4.2 -* [\[Sutter02\]](#Sutter02) §27 -* [\[Sutter04\]](#Sutter04) §18 +* [\[C++CS\]](../Bibliography.md) Item 50 +* [\[Cargill92\]](../Bibliography.md) pp. 77-79, 207 +* [\[Cline99\]](../Bibliography.md) §21.06, 21.12-13 +* [\[Henricson97\]](../Bibliography.md) pp. 110-114 +* [\[Koenig97\]](../Bibliography.md) Chapters 4, 11 +* [\[Meyers97\]](../Bibliography.md) §14 +* [\[Stroustrup00\]](../Bibliography.md) §12.4.2 +* [\[Sutter02\]](../Bibliography.md) §27 +* [\[Sutter04\]](../Bibliography.md) §18 -### Discussion: Usage of noexcept +### 토론: noexcept 사용 ??? -### Discussion: Destructors, deallocation, and swap must never fail +### 토론: 소멸자, 할당 해제, 스왑(swap)은 절대 실패해서는 안 된다 -Never allow an error to be reported from a destructor, a resource deallocation function (e.g., `operator delete`), or a `swap` function using `throw`. It is nearly impossible to write useful code if these operations can fail, and even if something does go wrong it nearly never makes any sense to retry. Specifically, types whose destructors may throw an exception are flatly forbidden from use with the C++ Standard Library. Most destructors are now implicitly `noexcept` by default. +소멸자, 리소스 할당 해제 함수(예: `delete 연산자`) 또는 `throw`를 사용하는 `swap` 함수에서 오류가 보고되는 것을 허용하지 마라. 이러한 연산이 실패할 수 있는 경우 유용한 코드를 작성하는 것은 거의 불가능하며, 문제가 발생하더라도 재시도하는 것은 거의 의미가 없다. 특히 소멸자가 예외를 던질 수 있는 타입은 C++ 표준 라이브러리에서 사용이 전면적으로 금지되어 있다. 현재 대부분의 소멸자는 암시적으로 기본값이 `noexcept`이다. -##### Example +##### 예제 +- `Nefarious` : 비난의 뜻이 강한 말로, 법이나 전통의 위반을 암시하고, 보통 극도로 사악한 것을 의미 ```c++ class Nefarious { public: - Nefarious() { /* code that could throw */ } // ok - ~Nefarious() { /* code that could throw */ } // BAD, should not throw + Nefarious() { /* 예외를 던질 수 있는 코드 */ } // ok + ~Nefarious() { /* 예외를 던질 수 있는 코드 */ } // BAD, 예외를 던져서는 안된다 // ... }; ``` -1. `Nefarious` objects are hard to use safely even as local variables: +1. `Nefarious` 객체는 지역 변수로도 안전하게 사용하기 어렵다: ```c++ void test(string& s) { - Nefarious n; // trouble brewing - string copy = s; // copy the string - } // destroy copy and then n + Nefarious n; // 점점 더 심각해지는 문제(trouble brewing) + string copy = s; // 문자열 복사 + } // copy 파과한 다음 n ``` -Here, copying `s` could throw, and if that throws and if `n`'s destructor then also throws, the program will exit via `std::terminate` because two exceptions can't be propagated simultaneously. +여기, `s`를 복사하면 예외가 발생하고, `n`의 소멸자도 예외가 발생하면 두 예외가 동시에 전파될 수 없으므로 `std::terminate`를 통해 프로그램이 종료된다. -2. Classes with `Nefarious` members or bases are also hard to use safely, because their destructors must invoke `Nefarious`' destructor, and are similarly poisoned by its poor behavior: +2. `Nefarious` 멤버나 기반으로 한 클래스 역시 소멸자가 반드시 `Nefarious`의 소멸자를 호출해야 하며, 마찬가지로 동작이 좋지 않아 안전하게 사용하기 어렵다: ```c++ class Innocent_bystander { - Nefarious member; // oops, poisons the enclosing class's destructor + Nefarious member; // 이런, 클래스의 소멸자를 둘러싼 독과 같습니다 // ... }; void test(string& s) { - Innocent_bystander i; // more trouble brewing - string copy2 = s; // copy the string - } // destroy copy and then i + Innocent_bystander i; // 훨씬 더 심각해지는 문제(more trouble brewing) + string copy2 = s; // 문자열 복사 + } // copy 파괴한 다음 i ``` -Here, if constructing `copy2` throws, we have the same problem because `i`'s destructor now also can throw, and if so we'll invoke `std::terminate`. +여기서 `copy`를 생성하면 `i`의 소멸자도 던질 수 있기 때문에 같은 문제가 발생하고, 그렇다면 `std::terminate`를 호출하게 된다. -3. You can't reliably create global or static `Nefarious` objects either: +3. 전역 또는 정적 `Nefarious` 객체도 안정적으로 생성할 수 없다: ```c++ - static Nefarious n; // oops, any destructor exception can't be caught + static Nefarious n; // 이런, 어떤 소멸자 예외도 잡을 수 없습니다 ``` -4. You can't reliably create arrays of `Nefarious`: +4. `Nefarious` 배열을 안정적으로 생성할 수 없다: ```c++ void test() { - std::array arr; // this line can std::terminate(!) + std::array arr; // 이 줄에서 std::terminate(!)가 가능합니다 } ``` -The behavior of arrays is undefined in the presence of destructors that throw because there is no reasonable rollback behavior that could ever be devised. Just think: What code can the compiler generate for constructing an `arr` where, if the fourth object's constructor throws, the code has to give up and in its cleanup mode tries to call the destructors of the already-constructed objects ... and one or more of those destructors throws? There is no satisfactory answer. +배열의 동작은 소멸자가 존재할 때 정의되지 않는데, 그 이유는 고안할 수 있는 합리적인 롤백 동작이 없기 때문이다. 생각해보자: 4번째 객체의 생성자가 예외를 던지면 코드가 포기하고 정리 모드에서 이미 생성된 객체의 소멸자를 호출하려고 시도하고 그 소멸자 중 하나 이상이 예외를 던지는 `arr`를 생성하기 위해 컴파일러가 생성할 수 있는 코드는 무엇인가? 여기에 만족할만한 정답은 없다. -5. You can't use `Nefarious` objects in standard containers: +5. 표준 컨테이너에서는 `Nefarious` 객체를 사용할 수 없다: ```c++ - std::vector vec(10); // this line can std::terminate() + std::vector vec(10); // 이 줄에서 std::terminate()가 가능합니다 ``` -The standard library forbids all destructors used with it from throwing. You can't store `Nefarious` objects in standard containers or use them with any other part of the standard library. +표준 라이브러리에서는 함께 사용되는 모든 소멸자가 예외를 던지는 것을 금지하고 있다. 표준 컨테이너에 `Nefarious` 객체를 저장하거나 표준 라이브러리의 다른 부분과 함께 사용할 수 없다. ##### Note -These are key functions that must not fail because they are necessary for the two key operations in transactional programming: to back out work if problems are encountered during processing, and to commit work if no problems occur. If there's no way to safely back out using no-fail operations, then no-fail rollback is impossible to implement. If there's no way to safely commit state changes using a no-fail operation (notably, but not limited to, `swap`), then no-fail commit is impossible to implement. +이는 트랜잭션 프로그래밍에서 처리 중 문제가 발생하면 작업을 철회하고 문제가 발생하지 않으면 작업을 커밋하는 두 가지 주요 작업에 필요하기 때문에 실패해서는 안 되는 핵심 기능이다. 실패없는 연산을 이용하여 안전하게 작업을 철회할 수 있는 방법이 없다면 무장애 롤백(no-fail rollback)을 구현할 수 없다. 장애없는 작업을 사용하여 상태 변경을 안전하게 커밋할 방법이 없는 경우(특히, `swap`을 포함하되 이에 국한되지 않음) 장애없는 커밋을 구현할 수 없다. -Consider the following advice and requirements found in the C++ Standard: +C++ 표준에 나와 있는 다음 조언과 요구 사항을 고려하라: -> If a destructor called during stack unwinding exits with an exception, terminate is called (15.5.1). So destructors should generally catch exceptions and not let them propagate out of the destructor. --[\[C++03\]](#Cplusplus03) §15.2(3) +> 스택 해제 중에 호출된 소멸자가 예외와 함께 종료되면 terminate가 호출된다 (15.5.1). 따라서 소멸자는 일반적으로 예외를 포착하고 예외가 소멸자 밖으로 전파되지 않도록 해야한다. --[\[C++03\]](../Bibliography.md) §15.2(3) > -> No destructor operation defined in the C++ Standard Library (including the destructor of any type that is used to instantiate a standard-library template) will throw an exception. --[\[C++03\]](#Cplusplus03) §17.4.4.8(3) +> C++ 표준 라이브러리에 정의된 소멸자 연산(표준 라이브러리 템플릿을 인스턴스화하는 데 사용되는 모든 유형의 소멸자 포함)은 예외를 발생시키지 않는다. --[\[C++03\]](../Bibliography.md) §17.4.4.8(3) -Deallocation functions, including specifically overloaded `operator delete` and `operator delete[]`, fall into the same category, because they too are used during cleanup in general, and during exception handling in particular, to back out of partial work that needs to be undone. -Besides destructors and deallocation functions, common error-safety techniques rely also on `swap` operations never failing -- in this case, not because they are used to implement a guaranteed rollback, but because they are used to implement a guaranteed commit. For example, here is an idiomatic implementation of `operator=` for a type `T` that performs copy construction followed by a call to a no-fail `swap`: +구체적으로 말하자면 과부하가 걸린 `delete 연산자`와 `delete[] 연산자`를 포함한 할당 해제 함수는 일반적으로 정리하는 동안, 특히 예외 처리 중에 실행 취소가 필요한 작업 부분을 철회하는 데 사용되기 때문에 같은 범주에 속한다. 소멸자와 할당 해제 함수 외에도 일반적인 에러 안전 기술은 `swap` 연산이 실패하지 않는 것에 의존하는데, 이 경우에는 연산이 실패하지 않는 것을 보장된 롤백을 구현하는 데 사용되기 때문이 아니라 보장된 커밋을 구현하는 데 사용되기 때문이다. 예를 들어, 다음은 복사 생성을 수행한 다음 실패하지 않는 `swap`을 호출하는 `T`유형에 대한 `operator=`의 관용적 구현이다: ```c++ - T& T::operator=(const T& other) { + T& T::operator=(const T& other) + { auto temp = other; swap(temp); return *this; } ``` -(See also Item 56. ???) +(또한 Item 56을 참고하라 ???) -Fortunately, when releasing a resource, the scope for failure is definitely smaller. If using exceptions as the error reporting mechanism, make sure such functions handle all exceptions and other errors that their internal processing might generate. (For exceptions, simply wrap everything sensitive that your destructor does in a `try/catch(...)` block.) This is particularly important because a destructor might be called in a crisis situation, such as failure to allocate a system resource (e.g., memory, files, locks, ports, windows, or other system objects). +다행히, 리소스를 해제할 때 실패할 수 있는 범위가 확실히 줄어든다. 예외를 오류 보고 메커니즘으로 사용하는 경우, 해당 함수가 내부 처리에서 발생할 수 있는 모든 예외 및 기타 오류를 처리하는지 확인하라. +(예외의 경우, 소멸자가 수행하는 모든 민감한 작업을 `try/catch(...)` 블록으로 감싸면 된다.) 이것은 특히 중요한데 시스템 리소스(예, 메모리, 파일, 잠금, 포트, 창 또는 기타 시스템 개체)를 할당하지 못하는 등의 위기 상황에서 소멸자가 호출될 수 있기 때문이다. -When using exceptions as your error handling mechanism, always document this behavior by declaring these functions `noexcept`. (See Item 75.) +예외를 오류 처리 메커니즘으로 사용할 때는, 항상 이러한 함수를 `noexcept`로 선언하여 이 동작을 문서화하라.(Item 75 참조) -**References**: [\[C++CS\]](#CplusplusCS) Item 51; [\[C++03\]](#Cplusplus03) §15.2(3), §17.4.4.8(3), [\[Meyers96\]](#Meyers96) §11, [\[Stroustrup00\]](#Stroustrup00) §14.4.7, §E.2-4, [\[Sutter00\]](#Sutter00) §8, §16, [\[Sutter02\]](#Sutter02) §18-19 +**References**: [\[C++CS\]](../Bibliography.md) Item 51; [\[C++03\]](../Bibliography.md) §15.2(3), §17.4.4.8(3), [\[Meyers96\]](../Bibliography.md) §11, [\[Stroustrup00\]](../Bibliography.md) §14.4.7, §E.2-4, [\[Sutter00\]](../Bibliography.md) §8, §16, [\[Sutter02\]](../Bibliography.md) §18-19 -## Define Copy, move, and destroy consistently +## 일관성 있는 복사, 이동, 소멸자를 정의하라 ##### Reason @@ -309,11 +326,11 @@ When using exceptions as your error handling mechanism, always document this beh ##### Note -If you define a copy constructor, you must also define a copy assignment operator. +복사 생성자를 정의하는 경우, 복사 할당 연산자도 정의해야 한다. ##### Note -If you define a move constructor, you must also define a move assignment operator. +이동 생성자를 정의하는 경우, 이동 할당 연산자도 정의해야 한다. ##### Example @@ -323,95 +340,95 @@ If you define a move constructor, you must also define a move assignment operato public: X(const X&) { /* stuff */ } - // BAD: failed to also define a copy assignment operator + // BAD: 복사 할당 연산자도 정의하지 못했습니다 X(x&&) noexcept { /* stuff */ } - // BAD: failed to also define a move assignment operator + // BAD: 이동 할당 연산자가 정의하지 못했습니다 }; X x1; X x2 = x1; // ok - x2 = x1; // pitfall: either fails to compile, or does something suspicious + x2 = x1; // 위험(pitfall): 컴파일에 실패하거나, 의심스러운 작업을 수행함 ``` -If you define a destructor, you should not use the compiler-generated copy or move operation; you probably need to define or suppress copy and/or move. +소멸자를 정의하는 경우, 컴파일러에서 생성된 복사 또는 이동 연산을 사용해서는 안 되며, 복사 및/또는 이동을 정의하거나 억제해야 할 수도 있다. ```c++ class X { HANDLE hnd; // ... public: - ~X() { /* custom stuff, such as closing hnd */ } - // suspicious: no mention of copying or moving -- what happens to hnd? + ~X() { /* 사용자 지정 항목(예: hnd 닫기) */ } + // 의심스러운 작업 : 복사 또는 이동에 대한 언급이 없음 + // - hnd는 어떻게 되는가? }; X x1; - X x2 = x1; // pitfall: either fails to compile, or does something suspicious - x2 = x1; // pitfall: either fails to compile, or does something suspicious + X x2 = x1; // 위험(pitfall): 컴파일에 실패하거나, 의심스러운 작업을 수행함 + x2 = x1; // 위험(pitfall): 컴파일에 실패하거나, 의심스러운 작업을 수행함 ``` -If you define copying, and any base or member has a type that defines a move operation, you should also define a move operation. +복사를 정의하고 기반 또는 멤버에 이동 연산을 정의하는 유형이 있는 경우 이동 연산도 정의해야 한다. ```c++ class X { - string s; // defines more efficient move operations - // ... other data members ... + string s; // 보다 효율적인 이동 연산을 정의 + // ... 다른 멤버 데이터들 ... public: X(const X&) { /* stuff */ } X& operator=(const X&) { /* stuff */ } - // BAD: failed to also define a move construction and move assignment - // (why wasn't the custom "stuff" repeated here?) + // BAD: 이동 생성 및 이동 할당도 정의하지 못함 + // (여기서 사용자 정의 "stuff"를 왜 반복하지 않았는가?) }; X test() { X local; // ... - return local; // pitfall: will be inefficient and/or do the wrong thing + return local; // 위험(pitfall): 비효율적이거나 잘못된 작업을 수행함 } ``` -If you define any of the copy constructor, copy assignment operator, or destructor, you probably should define the others. +복사 생성자, 복사 할당 연산자 또는 소멸자 중 하나를 정의하는 경우 다른 생성자도 정의해야 한다. ##### Note -If you need to define any of these five functions, it means you need it to do more than its default behavior -- and the five are asymmetrically interrelated. Here's how: +이 다섯 가지 함수 중 하나를 정의해야 한다는 것은 기본 동작 이상의 기능을 수행해야 한다는 의미이며, 이 다섯 가지 함수는 비대칭적으로 상호 연관되어 있다. 방법은 다음과 같다: -* If you write/disable either of the copy constructor or the copy assignment operator, you probably need to do the same for the other: If one does "special" work, probably so should the other because the two functions should have similar effects. (See Item 53, which expands on this point in isolation.) -* If you explicitly write the copying functions, you probably need to write the destructor: If the "special" work in the copy constructor is to allocate or duplicate some resource (e.g., memory, file, socket), you need to deallocate it in the destructor. -* If you explicitly write the destructor, you probably need to explicitly write or disable copying: If you have to write a non-trivial destructor, it's often because you need to manually release a resource that the object held. If so, it is likely that those resources require careful duplication, and then you need to pay attention to the way objects are copied and assigned, or disable copying completely. +* 복사 생성자나 복사 할당 연산자 중 하나를 작성/비활성화하는 경우 다른 하나에 대해서도 동일한 작업을 수행해야 할 것이다: 한 쪽이 "특별한" 작업을 수행한다면, 두 함수가 비슷한 효과를 가져야 하므로 다른 쪽도 그렇게 해야 할 것이다. (이 부분은 Item 53번 항목에서 자세히 설명한다) +* 복사 함수를 명시적으로 작성하는 경우 소멸자를 작성해야 할 수도 있다: 복사 생성자의 "특별한" 작업이 일부 리소스(예: 메모리, 파일, 소켓)를 할당하거나 복제하는 것이라면 소멸자에서 해당 리소스를 할당 해제해야 한다. +* 소멸자를 명시적으로 작성하는 경우 복사를 명시적으로 작성하거나 비활성화해야 할 수도 있다: 어떤 작업을 하는(non-trivial) 소멸자를 작성해야 하는 경우, 객체가 보유한 리소스를 수동으로 해제해야 하기 때문인 경우가 많다. 그렇다면 해당 리소스는 신중하게 복제해야 할 가능성이 높으므로 객체가 복사 및 할당되는 방식에 주의를 기울이거나 복사를 완전히 비활성화해야 한다. -In many cases, holding properly encapsulated resources using RAII "owning" objects can eliminate the need to write these operations yourself. (See Item 13.) +많은 경우 RAII "소유(owning)" 객체를 사용하여 적절하게 캡슐화된 리소스를 보유하면 이러한 작업을 직접 작성할 필요가 없다.(Item 13 참조) -Prefer compiler-generated (including `=default`) special members; only these can be classified as "trivial", and at least one major standard library vendor heavily optimizes for classes having trivial special members. This is likely to become common practice. +컴파일러가 생성해주는 (`=default` 포함) 특수 멤버를 선호하라. 이것들만 "아무 일도 하지 않은(trivial)"으로 분류할 수 있으며, 적어도 하나의 주요 표준 라이브러리 공급업체는 아무 일도 하지 않는 특수 멤버를 가진 클래스에 대해 훌륭하게 최적화한다. 이는 일반적인 관행이 될 가능성이 높다. -**Exceptions**: When any of the special functions are declared only to make them nonpublic or virtual, but without special semantics, it doesn't imply that the others are needed. -In rare cases, classes that have members of strange types (such as reference members) are an exception because they have peculiar copy semantics. -In a class holding a reference, you likely need to write the copy constructor and the assignment operator, but the default destructor already does the right thing. (Note that using a reference member is almost always wrong.) +**예외**: 특수 함수가 특별한 의미 없이 public이 아니거나 가상으로만 선언된 경우 다른 함수가 필요하다는 것을 의미하지는 않는다. 드물게 참조 멤버와 같은 이상한 유형의 멤버를 가진 클래스는 독특한 복사 의미론을 가지고 있기 떄문에 예외를 가진다. +참조를 보유한 클래스에서는 복사 생성자와 할당 연산자를 작성해야 하지만 기본 소멸자가 이미 올바른 작업을 수행한다. (참조 멤버를 사용하는 것은 거의 항상 잘못된 것이다.) -**References**: [\[C++CS\]](#CplusplusCS) Item 52; [\[Cline99\]](#Cline99) §30.01-14, [\[Koenig97\]](#Koenig97) §4, [\[Stroustrup00\]](#Stroustrup00) §5.5, §10.4, [\[SuttHysl04b\]](#SuttHysl04b) +**References**: [\[C++CS\]](../Bibliography.md) Item 52; [\[Cline99\]](../Bibliography.md) §30.01-14, [\[Koenig97\]](../Bibliography.md) §4, [\[Stroustrup00\]](../Bibliography.md) §5.5, §10.4, [\[SuttHysl04b\]](../Bibliography.md) -Resource management rule summary: +리소스 관리 규칙 요약: -* [Provide strong resource safety; that is, never leak anything that you think of as a resource](#Cr-safety) -* [Never throw while holding a resource not owned by a handle](#Cr-never) -* [A "raw" pointer or reference is never a resource handle](#Cr-raw) -* [Never let a pointer outlive the object it points to](#Cr-outlive) -* [Use templates to express containers (and other resource handles)](#Cr-templates) -* [Return containers by value (relying on move or copy elision for efficiency)](#Cr-value-return) -* [If a class is a resource handle, it needs a constructor, a destructor, and copy and/or move operations](#Cr-handle) -* [If a class is a container, give it an initializer-list constructor](#Cr-list) +* [강력한 리소스 안전성을 제공하라. 즉, 리소스라고 생각되면 어떤 것도 누수되지 않아야 한다](#Cr-safety) +* [핸들이 소유하지 않은 리소스를 잡은 채로 예외를 던지지 마라](#Cr-never) +* ["원시(raw)" 포인터 또는 참조는 절대 리소스 핸들이 아니다](#Cr-raw) +* [절대 포인터가 가리키는 객체보다 오래 지속되지 않도록 하라](#Cr-outlive) +* [템플릿을 사용하여 컨테이너(및 기타 리소스 핸들)를 표현하라](#Cr-templates) +* [(효율성을 위해 이동 또는 복사 생략에 의존하는)값으로 컨테이너 반환하라](#Cr-value-return) +* [클래스가 리소스 핸들인 경우 생성자, 소멸자, 복사 및/또는 이동 연산이 필요하다](#Cr-handle) +* [클래스가 컨테이너인 경우, 초기화리스트 생성자를 제공하라](#Cr-list) -### Discussion: Provide strong resource safety; that is, never leak anything that you think of as a resource +### 토론: 강력한 리소스 안전성을 제공하라. 즉, 리소스라고 생각되면 어떤 것도 누수되지 않아야 한다 ##### Reason -Prevent leaks. Leaks can lead to performance degradation, mysterious error, system crashes, and security violations. +누수를 방지하라. 누수는 성능 저하, 원인 모를 오류, 시스템 충돌, 보안 위반으로 이어질 수 있다. -**Alternative Formulation**: -Have every resource represented as an object of some class managing its lifetime. +**대안 방법**: +모든 리소스를 수명을 관리하는 일부 클래스의 객체로 표현하라 ##### Example @@ -425,7 +442,7 @@ Have every resource represented as an object of some class managing its lifetime }; ``` -This class is a resource handle. It manages the lifetime of the `T`s. To do so, `Vector` must define or delete [the set of special operations](???) (constructors, a destructor, etc.). +이 클래스는 리소스 핸들이다. 이 클래스는 `T`의 수명을 관리한다. 이를 위해, `Vector`는 [특수 연산 집합을 정의하거나 삭제해야 한다](???) (생성자, 소멸자, 등등). ##### Example @@ -433,13 +450,13 @@ This class is a resource handle. It manages the lifetime of the `T`s. To do so, ##### Enforcement -The basic technique for preventing leaks is to have every resource owned by a resource handle with a suitable destructor. A checker can find "naked `new`s". Given a list of C-style allocation functions (e.g., `fopen()`), a checker can also find uses that are not managed by a resource handle. In general, "naked pointers" can be viewed with suspicion, flagged, and/or analyzed. A complete list of resources cannot be generated without human input (the definition of "a resource" is necessarily too general), but a tool can be "parameterized" with a resource list. +누수를 방지하는 기본 기술은 리소스가 소유한 모든 리소스에 적절한 소멸자가 있는 핸들을 갖도록 하는 것이다. 검사기는 "naked `new`s"을 찾을 수 있다. C 스타일 할당 함수(예: `fopen()`)의 목록이 주어지면 검사기는 리소스 핸들이 관리하지 않는 사용처도 찾을 수 있다. 일반적으로 "naked pointers"는 의심스럽게 보고, 플래그를 지정하고, 분석할 수 있다. 사람의 입력 없이는 리소스의 전체 목록을 생성할 수 없지만("리소스"의 정의가 너무 일반적일 수밖에 없음), 리소스 목록으로 도구를 "매개 변수화"할 수 있다. -### Discussion: Never throw while holding a resource not owned by a handle +### 토론: 핸들이 소유하지 않은 리소스를 잡은 채로 예외를 던지지 마라 ##### Reason -That would be a leak. +이는 누수가 될 수 있다. ##### Example @@ -455,7 +472,7 @@ That would be a leak. } ``` -If `i == 0` the file handle for `a file` is leaked. On the other hand, the `ifstream` for `another file` will correctly close its file (upon destruction). If you must use an explicit pointer, rather than a resource handle with specific semantics, use a `unique_ptr` or a `shared_ptr` with a custom deleter: +만약 `i == 0`이면 `파일`에 대한 파일 핸들이 누수된다. 반면에 `다른 파일`에 대한 `ifstream`은 (파괴 시) 파일을 올바르게 닫는다. 특정 의미론을 가진 리소스 핸들이 아닌 명시적 포인터를 사용해야 하는 경우 사용자 정의 삭제자(custom deleter)가 있는 `unique_ptr` 또는 `shared_ptr`을 사용하라: ```c++ void f(int i) @@ -481,34 +498,34 @@ Better: ##### Enforcement -A checker must consider all "naked pointers" suspicious. -A checker probably must rely on a human-provided list of resources. -For starters, we know about the standard-library containers, `string`, and smart pointers. -The use of `span` and `string_span` should help a lot (they are not resource handles). +검사기는 모든 "날 포인터(naked pointers)"를 의심스러운 것으로 간주해야 한다. +검사기는 아마도 사람이 제공한 리소스 목록에 의존해야 할 것이다. +우선, 우리는 표준 라이브러리 컨테이너, `string`, 스마트 포인터에 대해 알고 있다. +`span`과 `string_span`을 사용하면 많은 도움이 될 것이다(리소스 핸들이 아님). -### Discussion: A "raw" pointer or reference is never a resource handle +### 토론: "원시(raw)" 포인터 또는 참조는 절대 리소스 핸들이 아니다 ##### Reason -To be able to distinguish owners from views. +소유자와 관찰자(view)를 구별할 수 있어야 한다. ##### Note -This is independent of how you "spell" pointer: `T*`, `T&`, `Ptr` and `Range` are not owners. +이것은 포인터를 "문법에 맞게 쓰는(spell)" 방법과는 무관하다: `T*`, `T&`, `Ptr` 및 `Range`는 소유자가 아니다. -### Discussion: Never let a pointer outlive the object it points to +### 토론: 절대 포인터가 가리키는 객체보다 오래 지속되지 않도록 하라 ##### Reason -To avoid extremely hard-to-find errors. Dereferencing such a pointer is undefined behavior and could lead to violations of the type system. +찾기 어려운 오류를 피하기 위해서다. 이러한 포인터를 역참조하는 것은 정의되지 않은 동작이며 타입 시스템을 위반할 수 있다. ##### Example ```c++ - string* bad() // really bad + string* bad() // 정말 안좋은 예시 { vector v = { "This", "will", "cause", "trouble", "!" }; - // leaking a pointer into a destroyed member of a destroyed object (v) + // 파괴된 객체 (v)의 파괴된 멤버로 포인터 누수 return &v[0]; } @@ -516,40 +533,40 @@ To avoid extremely hard-to-find errors. Dereferencing such a pointer is undefine { string* p = bad(); vector xx = {7, 8, 9}; - // undefined behavior: x may not be the string "This" + // 정의되지 않은 동작: x 는 "This" 문자열이 아닐 수도 있음 string x = *p; - // undefined behavior: we don't know what (if anything) is allocated a location p + // 정의되지 않은 동작: p 위치에 할당된 것이 무엇인지(있다면) 알 수 없음 *p = "Evil!"; } ``` -The `string`s of `v` are destroyed upon exit from `bad()` and so is `v` itself. The returned pointer points to unallocated memory on the free store. This memory (pointed into by `p`) may have been reallocated by the time `*p` is executed. There may be no `string` to read and a write through `p` could easily corrupt objects of unrelated types. +`bad()` 함수를 종료하면 `v`의 `문자열(string)`은 소멸되며 `v` 자체도 소멸된다. 반환된 포인터는 사용 가능한 저장소의 할당되지 않은 메모리를 가리킨다. 이 메모리(`p`가 가리키는) `*p`가 실행될 때 이미 재할당되었을 수 있다. 읽을 `문자열(string)`이 없을 수 있으며 `p`를 통한 쓰기는 관련 없는 유형의 객체를 쉽게 손상시킬 수 있다. ##### Enforcement -Most compilers already warn about simple cases and has the information to do more. Consider any pointer returned from a function suspect. Use containers, resource handles, and views (e.g., `span` known not to be resource handles) to lower the number of cases to be examined. For starters, consider every class with a destructor as resource handle. +대부분의 컴파일러는 이미 간단한 경우에 대해 경고하고 더 많은 작업을 수행할 수 있는 정보를 가지고 있다. 함수에서 반환된 포인터는 의심스러운 것으로 간주하라. 컨테이너, 리소스 핸들, 뷰(예: 리소스 핸들이 아닌 것으로 알려진 `span`)를 사용하여 검사할 항목의 수를 줄여라. 우선 소멸자가 있는 모든 클래스를 리소스 핸들로 간주하라. -### Discussion: Use templates to express containers (and other resource handles) +### 토론: 템플릿을 사용하여 컨테이너(및 기타 리소스 핸들)를 표현하라 ##### Reason -To provide statically type-safe manipulation of elements. +정적으로 타입이 안전한 요소 조작을 제공한다 ##### Example ```c++ template class Vector { // ... - T* elem; // point to sz elements of type T + T* elem; // 타입 T의 sz 요소를 가리킴 int sz; }; ``` -### Discussion: Return containers by value (relying on move or copy elision for efficiency) +### 토론: (효율성을 위해 이동 또는 복사 생략에 의존하는)값으로 컨테이너 반환하라 ##### Reason -To simplify code and eliminate a need for explicit memory management. To bring an object into a surrounding scope, thereby extending its lifetime. +코드가 단순해지고 명시적인 메모리 관리가 필요없다. 범위를 둘러싼 곳으로 객체를 가져와서 수명을 연장시킨다. **See also**: [F.20, the general item about "out" output values](#Rf-out) @@ -561,30 +578,30 @@ To simplify code and eliminate a need for explicit memory management. To bring a return ...; } - auto v = get_large_vector(); // return by value is ok, most modern compilers will do copy elision + auto v = get_large_vector(); // 값으로 반환해도 괜찮음, 대부분의 최신 컴파일러는 복사 생략을 수행함 ``` ##### Exception -See the Exceptions in [F.20](#Rf-out). +[F.20](#Rf-out)의 예외를 참조하라. ##### Enforcement -Check for pointers and references returned from functions and see if they are assigned to resource handles (e.g., to a `unique_ptr`). +함수에서 반환된 포인터와 참조를 확인하고 리소스 핸들(예: `unique_ptr`)에 할당되었는지 확인하라. -### Discussion: If a class is a resource handle, it needs a constructor, a destructor, and copy and/or move operations +### 토론: 클래스가 리소스 핸들인 경우 생성자, 소멸자, 복사 및/또는 이동 연산이 필요하다 ##### Reason -To provide complete control of the lifetime of the resource. To provide a coherent set of operations on the resource. +리소스의 수명을 완벽하게 제어할 수 있다. 리소스에 대한 일관된 연산 집합을 제공한다. ##### Example - ??? Messing with pointers + ??? 포인터 엉망으로 만드는 예제 ##### Note -If all members are resource handles, rely on the default special operations where possible. +모든 멤버가 리소스 핸들인 경우, 가능한 기본 특수 연산에 의존하라. ```c++ template struct Named { @@ -593,17 +610,17 @@ If all members are resource handles, rely on the default special operations wher }; ``` -Now `Named` has a default constructor, a destructor, and efficient copy and move operations, provided `T` has. +이제 `Named`에는 기본 생성자, 소멸자, 효율적인 복사 및 이동 연산이 있으며, `T`에는 이를 제공한다. ##### Enforcement -In general, a tool cannot know if a class is a resource handle. However, if a class has some of [the default operations](#SS-ctor), it should have all, and if a class has a member that is a resource handle, it should be considered as resource handle. +일반적으로 툴(tool)은 클래스가 리소스 핸들인지 여부를 알 수 없다. 그러나, 클래스가 [기본 연산](#SS-ctor) 중 일부를 가지고 있다면 모두 가지고 있어야 하며, 클래스에 리소스 핸들인 멤버가 있다면 리소스 핸들로 간주해야 한다. -### Discussion: If a class is a container, give it an initializer-list constructor +### 토론: 클래스가 컨테이너인 경우, 초기화리스트 생성자를 제공하라 ##### Reason -It is common to need an initial set of elements. +일반적으로 초기 요소 집합이 필요하다. ##### Example @@ -619,4 +636,4 @@ It is common to need an initial set of elements. ##### Enforcement -When is a class a container? ??? +클래스가 컨테이너인 경우? ???