From 4f9e4170df4d6886dbeb64aeb9c4fc1a230a6c8d Mon Sep 17 00:00:00 2001 From: twoo0220 Date: Sun, 17 Mar 2024 00:14:50 +0900 Subject: [PATCH 1/4] 24.03.17 - appendix/discussion --- sections/appendix/DIscussion.md | 408 +++++++++++++++++--------------- 1 file changed, 212 insertions(+), 196 deletions(-) diff --git a/sections/appendix/DIscussion.md b/sections/appendix/DIscussion.md index fbfe0a2..14fbbf1 100644 --- a/sections/appendix/DIscussion.md +++ b/sections/appendix/DIscussion.md @@ -1,12 +1,12 @@ # 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 +19,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 +150,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 +325,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 +339,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 +441,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 +449,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 +471,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 +497,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 +532,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 +577,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 +609,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 +635,4 @@ It is common to need an initial set of elements. ##### Enforcement -When is a class a container? ??? +클래스가 컨테이너인 경우? ??? From 45d965e87ac6acbc0ff6cd94c291a1ed30c64c66 Mon Sep 17 00:00:00 2001 From: twoo0220 Date: Thu, 4 Apr 2024 08:02:21 +0900 Subject: [PATCH 2/4] Performance section Update --- sections/Performance.md | 217 +++++++++++++++------------------------- 1 file changed, 78 insertions(+), 139 deletions(-) diff --git a/sections/Performance.md b/sections/Performance.md index 8462a1f..967c387 100644 --- a/sections/Performance.md +++ b/sections/Performance.md @@ -35,11 +35,11 @@ ##### Reason -If there is no need for optimization, the main result of the effort will be more errors and higher maintenance costs. +최적화가 필요하지 않은 경우, 최적화에 쏟은 노력의 주된 결과는 더 많은 오류와 더 높은 유지 관리 비용으로 이어집니다. ##### Note -Some people optimize out of habit or because it's fun. +일부는 습관적으로 또는 재밌어서 최적화를 하기도 합니다. ??? @@ -47,7 +47,7 @@ Some people optimize out of habit or because it's fun. ##### Reason -Elaborately optimized code is usually larger and harder to change than unoptimized code. +정교하게 최적화된 코드는 일반적으로 최적화되지 않은 코드보다 규모가 크고 변경하기가 더 어렵습니다. ??? @@ -55,28 +55,25 @@ Elaborately optimized code is usually larger and harder to change than unoptimiz ##### Reason -Optimizing a non-performance-critical part of a program has no effect on system performance. +프로그램의 성능에 중요하지 않은 부분을 최적화해도 시스템 성능에는 영향을 미치지 않습니다. ##### Note -If your program spends most of its time waiting for the web or for a human, optimization of in-memory computation is probably useless. +프로그램이 대부분의 시간을 웹이나 사람을 기다리는 데 소비한다면 내부 메모리 연산 최적화는 쓸모없을 수도 있습니다. -Put another way: If your program spends 4% of its processing time doing -computation A and 40% of its time doing computation B, a 50% improvement on A is -only as impactful as a 5% improvement on B. (If you don't even know how much -time is spent on A or B, see Per.1 and Per.2.) +다시 말해: 만약 프로그램이 처리 시간의 4%를 계산 A를 수행하고 40%의 시간을 계산 B에 사용하는 경우, A를 50% 개선하는 것은 B를 5% 개선하는 것만큼만 영향을 미칩니다(A 또는 B에 얼마나 많은 시간을 소비하는지 조차 모른다면, Per.1Per.2를 참조하세요). ### Per.4: 복잡한 코드가 간단한 코드보다 빠르다고 추측하지 마라 ##### Reason -Simple code can be very fast. Optimizers sometimes do marvels with simple code +간단한 코드는 매우 빠를 수 있습니다. 가끔 최적화는 간단한 코드로 놀라운 일을 해냅니다. ##### Example, good ```c++ - // clear expression of intent, fast execution + // 명확한 의도 표현, 빠른 실행 vector v(100000); @@ -87,7 +84,7 @@ Simple code can be very fast. Optimizers sometimes do marvels with simple code ##### Example, bad ```c++ - // intended to be faster, but is actually slower + // 더 빠르게 하려고 의도했지만, 실제로는 느립니다. vector v(100000); @@ -108,7 +105,7 @@ Simple code can be very fast. Optimizers sometimes do marvels with simple code ##### Reason -Low-level code sometimes inhibits optimizations. Optimizers sometimes do marvels with high-level code. +저수준(low-level) 코드는 때때로 최적화를 방해합니다. 최적화는 가끔 고수준(high-level) 코드로 놀라운 일을 해냅니다. ##### Note @@ -120,19 +117,15 @@ Low-level code sometimes inhibits optimizations. Optimizers sometimes do marvels ##### Reason -The field of performance is littered with myth and bogus folklore. -Modern hardware and optimizers defy naive assumptions; even experts are regularly surprised. +성능 분야는 미신과 가짜 속설(bogus folklore)로 가득 차 있습니다. 최신 하드웨어와 최적화 도구는 단순한 가정들을(naive assumptions) 뒤엎고 있으며 전문가들조차 종종 놀라움을 금지 못합니다. ##### Note -Getting good performance measurements can be hard and require specialized tools. +정확한 성능 측정은 어렵고 전문적인 도구가 필요할 수 있습니다. ##### Note -A few simple microbenchmarks using Unix `time` or the standard-library `` can help dispel the most obvious myths. -If you can't measure your complete system accurately, at least try to measure a few of your key operations and algorithms. -A profiler can help tell you which parts of your system are performance critical. -Often, you will be surprised. +Unix `time`이나 표준 라이브러리 ``를 사용한 몇 가지 간단한 마이크로 벤치마크는 가장 확실한 오해를 불식시키는 데 도움이 될 수 있습니다. 전체 시스템을 정확하게 측정할 수 없다면 최소한 몇 가지 주요 작업과 알고리듬을 측정해 보세요. 프로파일러(profiler)는 시스템의 어느 부분이 성능에 중요한지 알려줄 수 있습니다. 종종, 놀랄 것입니다. ??? @@ -140,34 +133,32 @@ Often, you will be surprised. ##### Reason -Because we often need to optimize the initial design. -Because a design that ignore the possibility of later improvement is hard to change. +초기 디자인을 최적화해야 하는 경우가 많기 때문입니다. +나중에 개선할 가능성을 무시한 디자인은 변경하기 어렵기 때문입니다. ##### Example -From the C (and C++) standard: +C (와 C++) 표준에서: ```c++ void qsort (void* base, size_t num, size_t size, int (*compar)(const void*, const void*)); ``` -When did you even want to sort memory? -Really, we sort sequences of elements, typically stored in containers. -A call to `qsort` throws away much useful information (e.g., the element type), forces the user to repeat information -already known (e.g., the element size), and forces the user to write extra code (e.g., a function to compare `double`s). -This implies added work for the programmer, is error-prone, and deprives the compiler of information needed for optimization. +언제 메모리를 정렬하고 싶어하십니까? +실제로, 우리는 일반적으로 컨테이너에 저장된 요소들의 순서를 정렬합니다. `qsort` 함수를 호출하면 많은 유용한 정보(예: 요소 유형)가 버리고, 사용자가 이미 알고 있는 정보(예: 요소 크기)를 반복해야 하며, 추가 코드(예: `double`을 비교하는 함수)를 작성하도록 강요합니다. +이것은 프로그래머의 추가 작업을 의미하고 오류가 발생하기 쉬우며 컴파일러에서 최적화에 필요한 정보를 빼앗아갑니다. ```c++ double data[100]; - // ... fill a ... - // 100 chunks of memory of sizeof(double) starting at - // address data using the order defined by compare_doubles + // compare_doubles 함수로 정의된 순서를 사용하여 주소 데이터에서 + // 시작하는 sizeof(double)의 메모리 덩어리(chunk) 100개 채우기 qsort(data, 100, sizeof(double), compare_doubles); ``` -From the point of view of interface design is that `qsort` throws away useful information. +인터페이스 디자인의 관점에서 볼 때 `qsort`는 유용한 정보를 버린다는 것입니다. +우리는 더 좋게할 수 있습니다 (C++ 98에서) We can do better (in C++98) ```c++ @@ -177,63 +168,37 @@ We can do better (in C++98) sort(data, data + 100); ``` -Here, we use the compiler's knowledge about the size of the array, the type of elements, and how to compare `double`s. +여기서는 배열의 크기, 요소의 유형, `double`을 비교하는 방법에 대한 컴파일러의 지식을 사용합니다. -With C++11 plus [concepts](#SS-concepts), we can do better still +C++ 11과 [개념(concepts)](#SS-concepts)을 사용하면 더 좋게 만들 수 있습니다. ```c++ - // Sortable specifies that c must be a - // random-access sequence of elements comparable with < + // Sortable 자료형은 < 와 비슷한 요소의 무작위 접근 순서여야 한다고 지정합니다. void sort(Sortable& c); sort(c); ``` -The key is to pass sufficient information for a good implementation to be chosen. -In this, the `sort` interfaces shown here still have a weakness: -They implicitly rely on the element type having less-than (`<`) defined. -To complete the interface, we need a second version that accepts a comparison criteria: +핵심은 좋은 구현이 선택될 수 있도록 충분한 정보를 전달하는 것입니다. 여기서 여기 표시된 `sort` 인터페이스는 여전히 약점이 있습니다: (`<`) 보다 작은 요소 유형이 정의된 것에 암시적으로 의존한다는 점입니다. 인터페이스를 완성하려면 비교 기준을 허용하는 두 번째 버전이 필요합니다: ```c++ - // compare elements of c using p + // p를사용하여 C의 요소와 비교하기 void sort(Sortable& c, Predicate> p); ``` -The standard-library specification of `sort` offers those two versions, -but the semantics is expressed in English rather than code using concepts. +표준 라이브러리 사양인 `sort`는 이 두 가지 버전을 제공합니다. 하지만 의미론(semantics)은 개념을 사용하는 코드가 아닌 영어로 표현됩니다. ##### Note -Premature optimization is said to be [the root of all evil](#Rper-Knuth), but that's not a reason to despise performance. -It is never premature to consider what makes a design amenable to improvement, and improved performance is a commonly desired improvement. -Aim to build a set of habits that by default results in efficient, maintainable, and optimizable code. -In particular, when you write a function that is not a one-off implementation detail, consider - -* Information passing: -Prefer clean [interfaces](#S-interfaces) carrying sufficient information for later improvement of implementation. -Note that information flows into and out of an implementation through the interfaces we provide. -* Compact data: By default, [use compact data](#Rper-compact), such as `std::vector` and [access it in a systematic fashion](#Rper-access). -If you think you need a linked structure, try to craft the interface so that this structure isn't seen by users. -* Function argument passing and return: -Distinguish between mutable and non-mutable data. -Don't impose a resource management burden on your users. -Don't impose spurious run-time indirections on your users. -Use [conventional ways](#Rf-conventional) of passing information through an interface; -unconventional and/or "optimized" ways of passing data can seriously complicate later reimplementation. -* Abstraction: -Don't overgeneralize; a design that tries to cater for every possible use (and misuse) and defers every design decision for later -(using compile-time or run-time indirections) is usually a complicated, bloated, hard-to-understand mess. -Generalize from concrete examples, preserving performance as we generalize. -Do not generalize based on mere speculation about future needs. -The ideal is zero-overhead generalization. -* Libraries: -Use libraries with good interfaces. -If no library is available build one yourself and imitate the interface style from a good library. -The [standard library](#S-stdlib) is a good first place to look for inspiration. -* Isolation: -Isolate your code from messy and/or old-style code by providing an interface of your choosing to it. -This is sometimes called "providing a wrapper" for the useful/necessary but messy code. -Don't let bad designs "bleed into" your code. +이른 최적화는 [모든 악의 근원](#Rper-Knuth)이라고 하지만, 그렇다고해서 성능을 경멸할 이유는 없습니다. 디자인을 개선할 수 있는 요소를 고려하는 것은 절대 이르지 않으며, 성능 개선은 일반적으로 바람직한 개선입니다.기본적으로 효율적이고, 유지 관리가 용이하며, 최적화 가능한 코드를 작성하는 일련의 습관을 기르는 것을 목표로 하세요. 특히 일회성 구현 세부 사항이 아닌 함수를 작성할 때는 다음 사항을 고려하세요. + +* 정보 전달: +나중에 구현을 개선할 수 있도록 충분한 정보를 전달하는 깔끔한 [인터페이스](#S-interfaces)를 선호하세요. 정보는 우리가 제공하는 인터페이스를 통해 구현으로 들어오고 나간다는 점을 유의하세요. +* 데이터 압축: 기본적으로, `std::vector`와 같은 [압축 데이터 사용](#Rper-compact)과 [체계적인 방식으로 접근](#Rper-access)하는 것을 권장합니다. 연결된 구조체가 필요하다면, 이 구조체가 사용자에게 보이지 않도록 인터페이스를 공들여 만드세요. +* 함수 인자 전달 및 반환: 수정 가능한 데이터와 수정 불가능한 데이터를 구분하세요. 사용자에게 리소스 관리 부담을 주지 마세요. 사용자에게 잘못된 런타임 간접 참조를 강요하지 마세요. 인터페이스를 통해 정보를 전달하는 [기존 방식](#Rf-conventional)을 사용하세요; 기존 방식이 아니거나 "최적화된" 데이터 전달 방식은 나중에 재구현을 심각할 정도로 복잡하게 만들 수 있습니다. +* 추상화: 지나치게 일반화하지 마세요. 가능한 모든 사용(및 오용)을 수용하려 하고 모든 디자인 결정을 나중으로(컴파일 타임 또는 런타임 간접 참조를 사용하여) 미루는 디자인은 일반적으로 복잡하고 규모가 커지며 이해하기 어렵도록 엉망진창이 됩니다. 구체적인 사례를 통해 일반화하되, 일반화할 때 성능을 유지합니다. 미래의 필요성에 대한 단순한 추측을 바탕으로 일반화하지 마세요. 가장 이상적인 것은 제로 오버헤드(zero-overhead) 일반화입니다. +* 라이브러리: 인터페이스가 좋은 라이브러리를 사용하세요. 사용할 수 있는 라이브러리가 없다면 직접 라이브러리를 만들고 좋은 라이브러리의 인터페이스 스타일을 모방하세요. [표준 라이브러리](#S-stdlib)는 가장 먼저 영감을 얻을 수 있는 좋은 곳입니다. +* 분리(isolation): 원하는 인터페이스를 제공하여 지저분하거나 오래된 스타일의 코드로부터 분리하세요. 이것은 유용하고 필요하지만 지저분한 코드를 위한 "래퍼(wrapper) 제공"이라고도 불립니다. 나쁜 디자인이 코드에 "스며들지" 않도록 하세요. ##### Example @@ -244,20 +209,18 @@ Consider: bool binary_search(ForwardIterator first, ForwardIterator last, const T& val); ``` -`binary_search(begin(c), end(c), 7)` will tell you whether `7` is in `c` or not. -However, it will not tell you where that `7` is or whether there are more than one `7`. +`binary_search(begin(c), end(c), 7)` 는 `7`이 `c`에 있는지 여부를 알려줍니다. 그러나 그 `7`이 어디에 있는지 아니면 `7`이 두 개 이상 있는지 여부를 알려주지 않습니다. -Sometimes, just passing the minimal amount of information back (here, `true` or `false`) is sufficient, but a good interface passes -needed information back to the caller. Therefore, the standard library also offers +때로는 최소한의 정보량(여기서는 `true` 또는 `false`)만 전달해도 충분하지만, 좋은 인터페이스 필요한 정보를 호출자에게 다시 전달합니다. 그러니 표준 라이브러리는 다음과 같은 기능도 제공합니다. ```c++ template ForwardIterator lower_bound(ForwardIterator first, ForwardIterator last, const T& val); ``` -`lower_bound` returns an iterator to the first match if any, otherwise to the first element greater than `val`, or `last` if no such element is found. +`lower_bound` 는 일치하는 요소가 있으면 첫 번째 요소로, 일치하지 않으면 `val`보다 큰 첫 번째 요소로, 일치하는 요소가 없으면 `last`로 반복자를 반환합니다. -However, `lower_bound` still doesn't return enough information for all uses, so the standard library also offers +그러나 `lower_bound`는 여전히 모든 용도에 대해 충분한 정보를 반환하지 않으므로 표준 라이브러리에서도 다음을 제공합니다 ```c++ template @@ -265,7 +228,7 @@ However, `lower_bound` still doesn't return enough information for all uses, so equal_range(ForwardIterator first, ForwardIterator last, const T& val); ``` -`equal_range` returns a `pair` of iterators specifying the first and one beyond last match. +`equal_range`는 첫 번째와 마지막 일치 이후를 지정하는 반복자의 `pair`를 반환합니다. ```c++ auto r = equal_range(begin(c), end(c), 7); @@ -273,57 +236,41 @@ However, `lower_bound` still doesn't return enough information for all uses, so cout << *p << '\n'; ``` -Obviously, these three interfaces are implemented by the same basic code. -They are simply three ways of presenting the basic binary search algorithm to users, -ranging from the simplest ("make simple things simple!") -to returning complete, but not always needed, information ("don't hide useful information"). -Naturally, crafting such a set of interfaces requires experience and domain knowledge. +물론 이 세 가지 인터페이스는 동일한 기본 코드로 구현됩니다. 가장 간단한 방법("간단한 것을 간단하게!")부터 완전한 정보를 반환하지만 항상 필요한 것은 아닌 정보("유용한 정보를 숨기기 마세요")에 이르기까지 기본적인 이진 탐색 알고리듬을 사용자에게 제시하는 세 가지 방법일 뿐입니다. 당연히 이러한 인터페이스를 제작하려면 경험과 도메인 지식이 필요합니다. ##### Note -Do not simply craft the interface to match the first implementation and the first use case you think of. -Once your first initial implementation is complete, review it; once you deploy it, mistakes will be hard to remedy. +단순히 첫 번째 구현과 가장 먼저 생각나는 사용 사례에 맞춰 인터페이스를 만들지 마세요. 첫 번째 초기 구현이 완료되면 이를 검토하세요. 일단 배포되면 실수를 수정하기 어렵습니다. ##### Note -A need for efficiency does not imply a need for [low-level code](#Rper-low). -High-level code does not imply slow or bloated. +효율성의 필요성은 [저수준 코드](#Rper-low)의 필요성을 의미하지는 않습니다. 고수준 코드는 느리거나 비대하다는 뜻이 아닙니다. ##### Note -Things have costs. -Don't be paranoid about costs (modern computers really are very fast), -but have a rough idea of the order of magnitude of cost of what you use. -For example, have a rough idea of the cost of -a memory access, -a function call, -a string comparison, -a system call, -a disk access, -and a message through a network. +모든 일에는 비용이 듭니다. 비용에 대해 지나치게 걱정할 필요는 없지만(최신 컴퓨터는 정말 빠릅니다), 자신이 사용하는 것의 비용 규모를 대략적으로 파악하세요. +예를 들어 메모리 접근, 함수 호출, 문자열 비교, 시스템 호출, 디스크 접근, 네트워크를 통한 메시지의 비용에 대해 대략적으로 파악하세요. ##### Note -If you can only think of one implementation, you probably don't have something for which you can devise a stable interface. -Maybe, it is just an implementation detail - not every piece of code needs a stable interface - but pause and consider. -One question that can be useful is -"what interface would be needed if this operation should be implemented using multiple threads? be vectorized?" +한 가지 구현만 떠올릴 수 있다면, 아마도 안정적인 인터페이스를 고안할 수 있는 무언가가 없는 것입니다. +모든 코드에 안정적인 인터페이스가 필요한 것은 아니므로 구현 세부 사항일 수도 있지만 잠시 멈춰고 생각해 보세요. +"이 작업을 멀티 스레드를 사용하여 구현해야만 한다면 어떤 인터페이스가 필요할까? 벡터화할까?"라는 질문이 유용할 수 있습니다. ##### Note -This rule does not contradict the [Don't optimize prematurely](#Rper-Knuth) rule. -It complements it encouraging developers enable later - appropriate and non-premature - optimization, if and where needed. +이 규칙은 [성급하게 최적화하지 마세요](#Rper-Knuth) 규칙과 모순되지 않습니다. 이 규칙은 개발자가 필요한 경우 적절하고 시기상조가 아닌 최적화를 나중에 활성화하도록 권장함으로써 이를 보완합니다. ##### Enforcement -Tricky. -Maybe looking for `void*` function arguments will find examples of interfaces that hinder later optimization. +까다롭습니다. +아마도 `void*` 함수 인자를 찾아보면 나중에 최적화를 방해하는 인터페이스의 예시를 찾을 수 있을 것입니다. ### Per.10: 정적 타입 시스템에 의지하라 ##### Reason -Type violations, weak types (e.g. `void*`s), and low-level code (e.g., manipulation of sequences as individual bytes) make the job of the optimizer much harder. Simple code often optimizes better than hand-crafted complex code. +타입 위반, 약한 타입(예: `void*`), 저수준 코드(예: 개별 바이트 단위로 순서 조작)는 최적화 도구의 작업을 훨씬 더 어렵게 만듭니다. 보통 간단한 코드가 수작업으로 만든 복잡한 코드보다 더 잘 최적화되는 경우가 많습니다. ??? @@ -331,15 +278,15 @@ Type violations, weak types (e.g. `void*`s), and low-level code (e.g., manipulat ##### Reason -To decrease code size and run time. -To avoid data races by using constants. -To catch errors at compile time (and thus eliminate the need for error-handling code). +코드 크기와 런타임을 줄이기 위해. +상수를 사용하여 데이터 경합을 피하기 위해. +컴파일 시 오류를 포착하기 위해(오류 처리 코드의 필요성을 제거합니다.) ##### Example ```c++ double square(double d) { return d*d; } - static double s2 = square(2); // old-style: dynamic initialization + static double s2 = square(2); // 옛날 방식 : 동적 초기화 constexpr double ntimes(double d, int n) // assume 0 <= n { @@ -347,68 +294,60 @@ To catch errors at compile time (and thus eliminate the need for error-handling while (n--) m *= d; return m; } - constexpr double s3 {ntimes(2, 3)}; // modern-style: compile-time initialization + constexpr double s3 {ntimes(2, 3)}; // 최신 방식 : 컴파일 시 초기화 ``` -Code like the initialization of `s2` isn't uncommon, especially for initialization that's a bit more complicated than `square()`. -However, compared to the initialization of `s3` there are two problems: +`s2`의 초기화와 같은 코드는 드물지 않으며, 특히 `square()`보다 조금 더 복잡한 초기화의 경우 더욱 그렇습니다. 그러나 `s3`의 초기화와 비교하면 두 가지 문제가 있습니다: -* we suffer the overhead of a function call at run time -* `s2` just might be accessed by another thread before the initialization happens. +* 런타임에 함수 호출로 인한 오버헤드를 겪게 됩니다. +* 초기화가 일어나기 전에 다른 스레드에서 `s2`에 접근할 수 있습니다. -Note: you can't have a data race on a constant. +Note: 상수를 두고 데이터 경합을 할 수 없습니다. ##### Example -Consider a popular technique for providing a handle for storing small objects in the handle itself and larger ones on the heap. +작은 객체는 핸들 자체에, 큰 객체는 힙에 저장할 수 있는 핸들을 제공하는 인기 있는 기법을 고려해 보세요. ```c++ constexpr int on_stack_max = 20; template - struct Scoped { // store a T in Scoped + struct Scoped { // Scoped 안에 T 저장 // ... T obj; }; template - struct On_heap { // store a T on the free store + struct On_heap { // 자유 저장소에(free store) T 저장 // ... T* objp; }; template using Handle = typename std::conditional<(sizeof(T) <= on_stack_max), - Scoped, // first alternative - On_heap // second alternative + Scoped, // 첫 번째 대안 + On_heap // 두 번쨰 대안 >::type; void f() { - Handle v1; // the double goes on the stack - Handle> v2; // the array goes on the free store + Handle v1; // double은 스택에 저장됩니다. + Handle> v2; // array는 자유 저장소에(free store) 저장됩니다. // ... } ``` -Assume that `Scoped` and `On_heap` provide compatible user interfaces. -Here we compute the optimal type to use at compile time. -There are similar techniques for selecting the optimal function to call. +`Scoped`와 `On_heap`이 호환 가능한 사용자 인터페이스를 제공한다고 가정합니다. 여기서는 컴파일 시 사용할 최적의 타입을 계산합니다. 호출할 최적의 함수를 선택하는 데는 비슷한 기법들이 있습니다. ##### Note -The ideal is {not} to try execute everything at compile time. -Obviously, most computations depend on inputs so they can't be moved to compile time, -but beyond that logical constraint is the fact that complex compile-time computation can seriously increase compile times -and complicate debugging. -It is even possible to slow down code by compile-time computation. -This is admittedly rare, but by factoring out a general computation into separate optimal sub-calculations it is possible to render the instruction cache less effective. +모든 것을 컴파일 타임에 실행하는 것이 이상적인 것은 *아닙니다*. 물론 대부분의 계산은 입력에 의존하기 때문에 컴파일 타임으로 옮길 수 없습니다. 하지만 이러한 논리적 제약을 넘어 복잡한 컴파일 타임 계산은 컴파일 시간을 심각하게 증가시킬 수 있고 디버깅을 복잡하게 만들 수 있습니다. 심지어 컴파일 시간 계산으로 인해 코드 속도가 느려질 수도 있습니다. 물론 이런 경우는 드물지만 일반적인 계산을 별도의 최적 하위 계산으로 분리하면 명령어 캐시의 효율성을 떨어뜨릴 수 있습니다. ##### Enforcement -* Look for simple functions that might be constexpr (but are not). -* Look for functions called with all constant-expression arguments. -* Look for macros that could be constexpr. +* constexpr 일 수 있지만 그렇지 않은 간단한 함수를 찾습니다. +* 모든 상수 표현식 인수로 호출되는 함수를 찾습니다. +* constexpr 이 될 수 있는 매크로를 찾습니다. ### Per.12: 불필요한 별칭을 제거하라 @@ -450,7 +389,7 @@ This is admittedly rare, but by factoring out a general computation into separat ##### Reason -Performance is very sensitive to cache performance and cache algorithms favor simple (usually linear) access to adjacent data. +성능은 캐시 성능에 매우 민감하며 캐시 알고리듬은 인전한 데이터에 대한 간단한(일반적으로 선형) 접근을 선호합니다. ##### Example From 07e0b83800c6f98ac495717597ab27cceb0868fc Mon Sep 17 00:00:00 2001 From: twoo0220 Date: Thu, 4 Apr 2024 08:03:39 +0900 Subject: [PATCH 3/4] Revert "Performance section Update" This reverts commit 45d965e87ac6acbc0ff6cd94c291a1ed30c64c66. --- sections/Performance.md | 217 +++++++++++++++++++++++++--------------- 1 file changed, 139 insertions(+), 78 deletions(-) diff --git a/sections/Performance.md b/sections/Performance.md index 967c387..8462a1f 100644 --- a/sections/Performance.md +++ b/sections/Performance.md @@ -35,11 +35,11 @@ ##### Reason -최적화가 필요하지 않은 경우, 최적화에 쏟은 노력의 주된 결과는 더 많은 오류와 더 높은 유지 관리 비용으로 이어집니다. +If there is no need for optimization, the main result of the effort will be more errors and higher maintenance costs. ##### Note -일부는 습관적으로 또는 재밌어서 최적화를 하기도 합니다. +Some people optimize out of habit or because it's fun. ??? @@ -47,7 +47,7 @@ ##### Reason -정교하게 최적화된 코드는 일반적으로 최적화되지 않은 코드보다 규모가 크고 변경하기가 더 어렵습니다. +Elaborately optimized code is usually larger and harder to change than unoptimized code. ??? @@ -55,25 +55,28 @@ ##### Reason -프로그램의 성능에 중요하지 않은 부분을 최적화해도 시스템 성능에는 영향을 미치지 않습니다. +Optimizing a non-performance-critical part of a program has no effect on system performance. ##### Note -프로그램이 대부분의 시간을 웹이나 사람을 기다리는 데 소비한다면 내부 메모리 연산 최적화는 쓸모없을 수도 있습니다. +If your program spends most of its time waiting for the web or for a human, optimization of in-memory computation is probably useless. -다시 말해: 만약 프로그램이 처리 시간의 4%를 계산 A를 수행하고 40%의 시간을 계산 B에 사용하는 경우, A를 50% 개선하는 것은 B를 5% 개선하는 것만큼만 영향을 미칩니다(A 또는 B에 얼마나 많은 시간을 소비하는지 조차 모른다면, Per.1Per.2를 참조하세요). +Put another way: If your program spends 4% of its processing time doing +computation A and 40% of its time doing computation B, a 50% improvement on A is +only as impactful as a 5% improvement on B. (If you don't even know how much +time is spent on A or B, see Per.1 and Per.2.) ### Per.4: 복잡한 코드가 간단한 코드보다 빠르다고 추측하지 마라 ##### Reason -간단한 코드는 매우 빠를 수 있습니다. 가끔 최적화는 간단한 코드로 놀라운 일을 해냅니다. +Simple code can be very fast. Optimizers sometimes do marvels with simple code ##### Example, good ```c++ - // 명확한 의도 표현, 빠른 실행 + // clear expression of intent, fast execution vector v(100000); @@ -84,7 +87,7 @@ href="#Rper-Knuth">Per.2를 참조하세요). ##### Example, bad ```c++ - // 더 빠르게 하려고 의도했지만, 실제로는 느립니다. + // intended to be faster, but is actually slower vector v(100000); @@ -105,7 +108,7 @@ href="#Rper-Knuth">Per.2를 참조하세요). ##### Reason -저수준(low-level) 코드는 때때로 최적화를 방해합니다. 최적화는 가끔 고수준(high-level) 코드로 놀라운 일을 해냅니다. +Low-level code sometimes inhibits optimizations. Optimizers sometimes do marvels with high-level code. ##### Note @@ -117,15 +120,19 @@ href="#Rper-Knuth">Per.2를 참조하세요). ##### Reason -성능 분야는 미신과 가짜 속설(bogus folklore)로 가득 차 있습니다. 최신 하드웨어와 최적화 도구는 단순한 가정들을(naive assumptions) 뒤엎고 있으며 전문가들조차 종종 놀라움을 금지 못합니다. +The field of performance is littered with myth and bogus folklore. +Modern hardware and optimizers defy naive assumptions; even experts are regularly surprised. ##### Note -정확한 성능 측정은 어렵고 전문적인 도구가 필요할 수 있습니다. +Getting good performance measurements can be hard and require specialized tools. ##### Note -Unix `time`이나 표준 라이브러리 ``를 사용한 몇 가지 간단한 마이크로 벤치마크는 가장 확실한 오해를 불식시키는 데 도움이 될 수 있습니다. 전체 시스템을 정확하게 측정할 수 없다면 최소한 몇 가지 주요 작업과 알고리듬을 측정해 보세요. 프로파일러(profiler)는 시스템의 어느 부분이 성능에 중요한지 알려줄 수 있습니다. 종종, 놀랄 것입니다. +A few simple microbenchmarks using Unix `time` or the standard-library `` can help dispel the most obvious myths. +If you can't measure your complete system accurately, at least try to measure a few of your key operations and algorithms. +A profiler can help tell you which parts of your system are performance critical. +Often, you will be surprised. ??? @@ -133,32 +140,34 @@ Unix `time`이나 표준 라이브러리 ``를 사용한 몇 가지 간 ##### Reason -초기 디자인을 최적화해야 하는 경우가 많기 때문입니다. -나중에 개선할 가능성을 무시한 디자인은 변경하기 어렵기 때문입니다. +Because we often need to optimize the initial design. +Because a design that ignore the possibility of later improvement is hard to change. ##### Example -C (와 C++) 표준에서: +From the C (and C++) standard: ```c++ void qsort (void* base, size_t num, size_t size, int (*compar)(const void*, const void*)); ``` -언제 메모리를 정렬하고 싶어하십니까? -실제로, 우리는 일반적으로 컨테이너에 저장된 요소들의 순서를 정렬합니다. `qsort` 함수를 호출하면 많은 유용한 정보(예: 요소 유형)가 버리고, 사용자가 이미 알고 있는 정보(예: 요소 크기)를 반복해야 하며, 추가 코드(예: `double`을 비교하는 함수)를 작성하도록 강요합니다. -이것은 프로그래머의 추가 작업을 의미하고 오류가 발생하기 쉬우며 컴파일러에서 최적화에 필요한 정보를 빼앗아갑니다. +When did you even want to sort memory? +Really, we sort sequences of elements, typically stored in containers. +A call to `qsort` throws away much useful information (e.g., the element type), forces the user to repeat information +already known (e.g., the element size), and forces the user to write extra code (e.g., a function to compare `double`s). +This implies added work for the programmer, is error-prone, and deprives the compiler of information needed for optimization. ```c++ double data[100]; + // ... fill a ... - // compare_doubles 함수로 정의된 순서를 사용하여 주소 데이터에서 - // 시작하는 sizeof(double)의 메모리 덩어리(chunk) 100개 채우기 + // 100 chunks of memory of sizeof(double) starting at + // address data using the order defined by compare_doubles qsort(data, 100, sizeof(double), compare_doubles); ``` -인터페이스 디자인의 관점에서 볼 때 `qsort`는 유용한 정보를 버린다는 것입니다. +From the point of view of interface design is that `qsort` throws away useful information. -우리는 더 좋게할 수 있습니다 (C++ 98에서) We can do better (in C++98) ```c++ @@ -168,37 +177,63 @@ We can do better (in C++98) sort(data, data + 100); ``` -여기서는 배열의 크기, 요소의 유형, `double`을 비교하는 방법에 대한 컴파일러의 지식을 사용합니다. +Here, we use the compiler's knowledge about the size of the array, the type of elements, and how to compare `double`s. -C++ 11과 [개념(concepts)](#SS-concepts)을 사용하면 더 좋게 만들 수 있습니다. +With C++11 plus [concepts](#SS-concepts), we can do better still ```c++ - // Sortable 자료형은 < 와 비슷한 요소의 무작위 접근 순서여야 한다고 지정합니다. + // Sortable specifies that c must be a + // random-access sequence of elements comparable with < void sort(Sortable& c); sort(c); ``` -핵심은 좋은 구현이 선택될 수 있도록 충분한 정보를 전달하는 것입니다. 여기서 여기 표시된 `sort` 인터페이스는 여전히 약점이 있습니다: (`<`) 보다 작은 요소 유형이 정의된 것에 암시적으로 의존한다는 점입니다. 인터페이스를 완성하려면 비교 기준을 허용하는 두 번째 버전이 필요합니다: +The key is to pass sufficient information for a good implementation to be chosen. +In this, the `sort` interfaces shown here still have a weakness: +They implicitly rely on the element type having less-than (`<`) defined. +To complete the interface, we need a second version that accepts a comparison criteria: ```c++ - // p를사용하여 C의 요소와 비교하기 + // compare elements of c using p void sort(Sortable& c, Predicate> p); ``` -표준 라이브러리 사양인 `sort`는 이 두 가지 버전을 제공합니다. 하지만 의미론(semantics)은 개념을 사용하는 코드가 아닌 영어로 표현됩니다. +The standard-library specification of `sort` offers those two versions, +but the semantics is expressed in English rather than code using concepts. ##### Note -이른 최적화는 [모든 악의 근원](#Rper-Knuth)이라고 하지만, 그렇다고해서 성능을 경멸할 이유는 없습니다. 디자인을 개선할 수 있는 요소를 고려하는 것은 절대 이르지 않으며, 성능 개선은 일반적으로 바람직한 개선입니다.기본적으로 효율적이고, 유지 관리가 용이하며, 최적화 가능한 코드를 작성하는 일련의 습관을 기르는 것을 목표로 하세요. 특히 일회성 구현 세부 사항이 아닌 함수를 작성할 때는 다음 사항을 고려하세요. - -* 정보 전달: -나중에 구현을 개선할 수 있도록 충분한 정보를 전달하는 깔끔한 [인터페이스](#S-interfaces)를 선호하세요. 정보는 우리가 제공하는 인터페이스를 통해 구현으로 들어오고 나간다는 점을 유의하세요. -* 데이터 압축: 기본적으로, `std::vector`와 같은 [압축 데이터 사용](#Rper-compact)과 [체계적인 방식으로 접근](#Rper-access)하는 것을 권장합니다. 연결된 구조체가 필요하다면, 이 구조체가 사용자에게 보이지 않도록 인터페이스를 공들여 만드세요. -* 함수 인자 전달 및 반환: 수정 가능한 데이터와 수정 불가능한 데이터를 구분하세요. 사용자에게 리소스 관리 부담을 주지 마세요. 사용자에게 잘못된 런타임 간접 참조를 강요하지 마세요. 인터페이스를 통해 정보를 전달하는 [기존 방식](#Rf-conventional)을 사용하세요; 기존 방식이 아니거나 "최적화된" 데이터 전달 방식은 나중에 재구현을 심각할 정도로 복잡하게 만들 수 있습니다. -* 추상화: 지나치게 일반화하지 마세요. 가능한 모든 사용(및 오용)을 수용하려 하고 모든 디자인 결정을 나중으로(컴파일 타임 또는 런타임 간접 참조를 사용하여) 미루는 디자인은 일반적으로 복잡하고 규모가 커지며 이해하기 어렵도록 엉망진창이 됩니다. 구체적인 사례를 통해 일반화하되, 일반화할 때 성능을 유지합니다. 미래의 필요성에 대한 단순한 추측을 바탕으로 일반화하지 마세요. 가장 이상적인 것은 제로 오버헤드(zero-overhead) 일반화입니다. -* 라이브러리: 인터페이스가 좋은 라이브러리를 사용하세요. 사용할 수 있는 라이브러리가 없다면 직접 라이브러리를 만들고 좋은 라이브러리의 인터페이스 스타일을 모방하세요. [표준 라이브러리](#S-stdlib)는 가장 먼저 영감을 얻을 수 있는 좋은 곳입니다. -* 분리(isolation): 원하는 인터페이스를 제공하여 지저분하거나 오래된 스타일의 코드로부터 분리하세요. 이것은 유용하고 필요하지만 지저분한 코드를 위한 "래퍼(wrapper) 제공"이라고도 불립니다. 나쁜 디자인이 코드에 "스며들지" 않도록 하세요. +Premature optimization is said to be [the root of all evil](#Rper-Knuth), but that's not a reason to despise performance. +It is never premature to consider what makes a design amenable to improvement, and improved performance is a commonly desired improvement. +Aim to build a set of habits that by default results in efficient, maintainable, and optimizable code. +In particular, when you write a function that is not a one-off implementation detail, consider + +* Information passing: +Prefer clean [interfaces](#S-interfaces) carrying sufficient information for later improvement of implementation. +Note that information flows into and out of an implementation through the interfaces we provide. +* Compact data: By default, [use compact data](#Rper-compact), such as `std::vector` and [access it in a systematic fashion](#Rper-access). +If you think you need a linked structure, try to craft the interface so that this structure isn't seen by users. +* Function argument passing and return: +Distinguish between mutable and non-mutable data. +Don't impose a resource management burden on your users. +Don't impose spurious run-time indirections on your users. +Use [conventional ways](#Rf-conventional) of passing information through an interface; +unconventional and/or "optimized" ways of passing data can seriously complicate later reimplementation. +* Abstraction: +Don't overgeneralize; a design that tries to cater for every possible use (and misuse) and defers every design decision for later +(using compile-time or run-time indirections) is usually a complicated, bloated, hard-to-understand mess. +Generalize from concrete examples, preserving performance as we generalize. +Do not generalize based on mere speculation about future needs. +The ideal is zero-overhead generalization. +* Libraries: +Use libraries with good interfaces. +If no library is available build one yourself and imitate the interface style from a good library. +The [standard library](#S-stdlib) is a good first place to look for inspiration. +* Isolation: +Isolate your code from messy and/or old-style code by providing an interface of your choosing to it. +This is sometimes called "providing a wrapper" for the useful/necessary but messy code. +Don't let bad designs "bleed into" your code. ##### Example @@ -209,18 +244,20 @@ Consider: bool binary_search(ForwardIterator first, ForwardIterator last, const T& val); ``` -`binary_search(begin(c), end(c), 7)` 는 `7`이 `c`에 있는지 여부를 알려줍니다. 그러나 그 `7`이 어디에 있는지 아니면 `7`이 두 개 이상 있는지 여부를 알려주지 않습니다. +`binary_search(begin(c), end(c), 7)` will tell you whether `7` is in `c` or not. +However, it will not tell you where that `7` is or whether there are more than one `7`. -때로는 최소한의 정보량(여기서는 `true` 또는 `false`)만 전달해도 충분하지만, 좋은 인터페이스 필요한 정보를 호출자에게 다시 전달합니다. 그러니 표준 라이브러리는 다음과 같은 기능도 제공합니다. +Sometimes, just passing the minimal amount of information back (here, `true` or `false`) is sufficient, but a good interface passes +needed information back to the caller. Therefore, the standard library also offers ```c++ template ForwardIterator lower_bound(ForwardIterator first, ForwardIterator last, const T& val); ``` -`lower_bound` 는 일치하는 요소가 있으면 첫 번째 요소로, 일치하지 않으면 `val`보다 큰 첫 번째 요소로, 일치하는 요소가 없으면 `last`로 반복자를 반환합니다. +`lower_bound` returns an iterator to the first match if any, otherwise to the first element greater than `val`, or `last` if no such element is found. -그러나 `lower_bound`는 여전히 모든 용도에 대해 충분한 정보를 반환하지 않으므로 표준 라이브러리에서도 다음을 제공합니다 +However, `lower_bound` still doesn't return enough information for all uses, so the standard library also offers ```c++ template @@ -228,7 +265,7 @@ Consider: equal_range(ForwardIterator first, ForwardIterator last, const T& val); ``` -`equal_range`는 첫 번째와 마지막 일치 이후를 지정하는 반복자의 `pair`를 반환합니다. +`equal_range` returns a `pair` of iterators specifying the first and one beyond last match. ```c++ auto r = equal_range(begin(c), end(c), 7); @@ -236,41 +273,57 @@ Consider: cout << *p << '\n'; ``` -물론 이 세 가지 인터페이스는 동일한 기본 코드로 구현됩니다. 가장 간단한 방법("간단한 것을 간단하게!")부터 완전한 정보를 반환하지만 항상 필요한 것은 아닌 정보("유용한 정보를 숨기기 마세요")에 이르기까지 기본적인 이진 탐색 알고리듬을 사용자에게 제시하는 세 가지 방법일 뿐입니다. 당연히 이러한 인터페이스를 제작하려면 경험과 도메인 지식이 필요합니다. +Obviously, these three interfaces are implemented by the same basic code. +They are simply three ways of presenting the basic binary search algorithm to users, +ranging from the simplest ("make simple things simple!") +to returning complete, but not always needed, information ("don't hide useful information"). +Naturally, crafting such a set of interfaces requires experience and domain knowledge. ##### Note -단순히 첫 번째 구현과 가장 먼저 생각나는 사용 사례에 맞춰 인터페이스를 만들지 마세요. 첫 번째 초기 구현이 완료되면 이를 검토하세요. 일단 배포되면 실수를 수정하기 어렵습니다. +Do not simply craft the interface to match the first implementation and the first use case you think of. +Once your first initial implementation is complete, review it; once you deploy it, mistakes will be hard to remedy. ##### Note -효율성의 필요성은 [저수준 코드](#Rper-low)의 필요성을 의미하지는 않습니다. 고수준 코드는 느리거나 비대하다는 뜻이 아닙니다. +A need for efficiency does not imply a need for [low-level code](#Rper-low). +High-level code does not imply slow or bloated. ##### Note -모든 일에는 비용이 듭니다. 비용에 대해 지나치게 걱정할 필요는 없지만(최신 컴퓨터는 정말 빠릅니다), 자신이 사용하는 것의 비용 규모를 대략적으로 파악하세요. -예를 들어 메모리 접근, 함수 호출, 문자열 비교, 시스템 호출, 디스크 접근, 네트워크를 통한 메시지의 비용에 대해 대략적으로 파악하세요. +Things have costs. +Don't be paranoid about costs (modern computers really are very fast), +but have a rough idea of the order of magnitude of cost of what you use. +For example, have a rough idea of the cost of +a memory access, +a function call, +a string comparison, +a system call, +a disk access, +and a message through a network. ##### Note -한 가지 구현만 떠올릴 수 있다면, 아마도 안정적인 인터페이스를 고안할 수 있는 무언가가 없는 것입니다. -모든 코드에 안정적인 인터페이스가 필요한 것은 아니므로 구현 세부 사항일 수도 있지만 잠시 멈춰고 생각해 보세요. -"이 작업을 멀티 스레드를 사용하여 구현해야만 한다면 어떤 인터페이스가 필요할까? 벡터화할까?"라는 질문이 유용할 수 있습니다. +If you can only think of one implementation, you probably don't have something for which you can devise a stable interface. +Maybe, it is just an implementation detail - not every piece of code needs a stable interface - but pause and consider. +One question that can be useful is +"what interface would be needed if this operation should be implemented using multiple threads? be vectorized?" ##### Note -이 규칙은 [성급하게 최적화하지 마세요](#Rper-Knuth) 규칙과 모순되지 않습니다. 이 규칙은 개발자가 필요한 경우 적절하고 시기상조가 아닌 최적화를 나중에 활성화하도록 권장함으로써 이를 보완합니다. +This rule does not contradict the [Don't optimize prematurely](#Rper-Knuth) rule. +It complements it encouraging developers enable later - appropriate and non-premature - optimization, if and where needed. ##### Enforcement -까다롭습니다. -아마도 `void*` 함수 인자를 찾아보면 나중에 최적화를 방해하는 인터페이스의 예시를 찾을 수 있을 것입니다. +Tricky. +Maybe looking for `void*` function arguments will find examples of interfaces that hinder later optimization. ### Per.10: 정적 타입 시스템에 의지하라 ##### Reason -타입 위반, 약한 타입(예: `void*`), 저수준 코드(예: 개별 바이트 단위로 순서 조작)는 최적화 도구의 작업을 훨씬 더 어렵게 만듭니다. 보통 간단한 코드가 수작업으로 만든 복잡한 코드보다 더 잘 최적화되는 경우가 많습니다. +Type violations, weak types (e.g. `void*`s), and low-level code (e.g., manipulation of sequences as individual bytes) make the job of the optimizer much harder. Simple code often optimizes better than hand-crafted complex code. ??? @@ -278,15 +331,15 @@ Consider: ##### Reason -코드 크기와 런타임을 줄이기 위해. -상수를 사용하여 데이터 경합을 피하기 위해. -컴파일 시 오류를 포착하기 위해(오류 처리 코드의 필요성을 제거합니다.) +To decrease code size and run time. +To avoid data races by using constants. +To catch errors at compile time (and thus eliminate the need for error-handling code). ##### Example ```c++ double square(double d) { return d*d; } - static double s2 = square(2); // 옛날 방식 : 동적 초기화 + static double s2 = square(2); // old-style: dynamic initialization constexpr double ntimes(double d, int n) // assume 0 <= n { @@ -294,60 +347,68 @@ Consider: while (n--) m *= d; return m; } - constexpr double s3 {ntimes(2, 3)}; // 최신 방식 : 컴파일 시 초기화 + constexpr double s3 {ntimes(2, 3)}; // modern-style: compile-time initialization ``` -`s2`의 초기화와 같은 코드는 드물지 않으며, 특히 `square()`보다 조금 더 복잡한 초기화의 경우 더욱 그렇습니다. 그러나 `s3`의 초기화와 비교하면 두 가지 문제가 있습니다: +Code like the initialization of `s2` isn't uncommon, especially for initialization that's a bit more complicated than `square()`. +However, compared to the initialization of `s3` there are two problems: -* 런타임에 함수 호출로 인한 오버헤드를 겪게 됩니다. -* 초기화가 일어나기 전에 다른 스레드에서 `s2`에 접근할 수 있습니다. +* we suffer the overhead of a function call at run time +* `s2` just might be accessed by another thread before the initialization happens. -Note: 상수를 두고 데이터 경합을 할 수 없습니다. +Note: you can't have a data race on a constant. ##### Example -작은 객체는 핸들 자체에, 큰 객체는 힙에 저장할 수 있는 핸들을 제공하는 인기 있는 기법을 고려해 보세요. +Consider a popular technique for providing a handle for storing small objects in the handle itself and larger ones on the heap. ```c++ constexpr int on_stack_max = 20; template - struct Scoped { // Scoped 안에 T 저장 + struct Scoped { // store a T in Scoped // ... T obj; }; template - struct On_heap { // 자유 저장소에(free store) T 저장 + struct On_heap { // store a T on the free store // ... T* objp; }; template using Handle = typename std::conditional<(sizeof(T) <= on_stack_max), - Scoped, // 첫 번째 대안 - On_heap // 두 번쨰 대안 + Scoped, // first alternative + On_heap // second alternative >::type; void f() { - Handle v1; // double은 스택에 저장됩니다. - Handle> v2; // array는 자유 저장소에(free store) 저장됩니다. + Handle v1; // the double goes on the stack + Handle> v2; // the array goes on the free store // ... } ``` -`Scoped`와 `On_heap`이 호환 가능한 사용자 인터페이스를 제공한다고 가정합니다. 여기서는 컴파일 시 사용할 최적의 타입을 계산합니다. 호출할 최적의 함수를 선택하는 데는 비슷한 기법들이 있습니다. +Assume that `Scoped` and `On_heap` provide compatible user interfaces. +Here we compute the optimal type to use at compile time. +There are similar techniques for selecting the optimal function to call. ##### Note -모든 것을 컴파일 타임에 실행하는 것이 이상적인 것은 *아닙니다*. 물론 대부분의 계산은 입력에 의존하기 때문에 컴파일 타임으로 옮길 수 없습니다. 하지만 이러한 논리적 제약을 넘어 복잡한 컴파일 타임 계산은 컴파일 시간을 심각하게 증가시킬 수 있고 디버깅을 복잡하게 만들 수 있습니다. 심지어 컴파일 시간 계산으로 인해 코드 속도가 느려질 수도 있습니다. 물론 이런 경우는 드물지만 일반적인 계산을 별도의 최적 하위 계산으로 분리하면 명령어 캐시의 효율성을 떨어뜨릴 수 있습니다. +The ideal is {not} to try execute everything at compile time. +Obviously, most computations depend on inputs so they can't be moved to compile time, +but beyond that logical constraint is the fact that complex compile-time computation can seriously increase compile times +and complicate debugging. +It is even possible to slow down code by compile-time computation. +This is admittedly rare, but by factoring out a general computation into separate optimal sub-calculations it is possible to render the instruction cache less effective. ##### Enforcement -* constexpr 일 수 있지만 그렇지 않은 간단한 함수를 찾습니다. -* 모든 상수 표현식 인수로 호출되는 함수를 찾습니다. -* constexpr 이 될 수 있는 매크로를 찾습니다. +* Look for simple functions that might be constexpr (but are not). +* Look for functions called with all constant-expression arguments. +* Look for macros that could be constexpr. ### Per.12: 불필요한 별칭을 제거하라 @@ -389,7 +450,7 @@ Note: 상수를 두고 데이터 경합을 할 수 없습니다. ##### Reason -성능은 캐시 성능에 매우 민감하며 캐시 알고리듬은 인전한 데이터에 대한 간단한(일반적으로 선형) 접근을 선호합니다. +Performance is very sensitive to cache performance and cache algorithms favor simple (usually linear) access to adjacent data. ##### Example From 9c31dbf2d704fc09c90b5e70f423d436764c5f8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=81=AC=EC=8D=B8?= Date: Tue, 14 May 2024 01:00:05 +0900 Subject: [PATCH 4/4] Update sections/appendix/DIscussion.md Co-authored-by: Park DongHa --- sections/appendix/DIscussion.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sections/appendix/DIscussion.md b/sections/appendix/DIscussion.md index 14fbbf1..783dc94 100644 --- a/sections/appendix/DIscussion.md +++ b/sections/appendix/DIscussion.md @@ -6,7 +6,8 @@ ### 토론: 멤버 변수들을 멤버 선언 순서에 따라 정의하고 초기화하라 -멤버 변수들은 항상 클래스의 정의부에서 선언된 순서에 따라 초기화되므로, 생성자의 초기화리스트에 그 순서대로 작성해야 한다. 다른 순서로 작성하는 것은 멤버 변수들이 눈에 보이는 순서대로 실행되지 않아 순서에 종속적인 버그를 찾기 어렵게 만들며 코드를 헷갈리게할 뿐이다. +멤버 변수들은 항상 클래스의 정의부에서 선언된 순서에 따라 초기화되므로, 생성자의 초기화리스트에 그 순서대로 작성해야 한다. +선언과 다른 순서로 작성하는 것은 의미를 알 수 없는 코드를 만드는데, 멤버 변수들이 눈에 보이는 순서대로 실행되지 않아 순서에 종속적인 버그를 찾기 어렵기 때문이다. ```c++ class Employee {