Effective C++读书笔记 Part2

Effective_CPP_Note2

:first-child {
margin-top: 0;
}
blockquote > :last-child {
margin-bottom: 15px;
}
h1 {
text-transform: uppercase;
font-weight: bold;
border-bottom: 1px solid;
}
h2 {
border-bottom: 1px solid;
}
h3,
h4,
h5,
h6 {
border-bottom: none;
}
html * {
color: #657b83;
}
html body {
background-color: #fdf6e3;
}
html h1,
html h2,
html h3,
html h4,
html h5,
html h6 {
color: #586e75;
border-color: #657b83;
}
html a,
html a:active,
html a:visited {
color: #586e75;
}
html a:hover {
background-color: #eee8d5;
}
html pre {
color: #586e75;
background-color: #eee8d5;
}
html a,
html a:active,
html a:visited,
html code.url {
color: #b58900;
}
html h1 {
color: #b58900;
}
html h2,
html h3,
html h4,
html h5,
html h6 {
color: #b58900;
}

@media print {
body {
margin: 0;
}
* {
color: #000 !important;
}
}
-->
code[class*="language-"],
pre[class*="language-"] {
background-color: #fdfdfd;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
margin-bottom: 1em;
}

/* Inline code */
:not(pre) > code[class*="language-"] {
position: relative;
padding: .2em;
-webkit-border-radius: 0.3em;
-moz-border-radius: 0.3em;
-ms-border-radius: 0.3em;
-o-border-radius: 0.3em;
border-radius: 0.3em;
color: #c92c2c;
border: 1px solid rgba(0, 0, 0, 0.1);
}

pre[class*="language-"]:before,
pre[class*="language-"]:after {
content: ‘‘;
z-index: -2;
display: block;
position: absolute;
bottom: 0.75em;
left: 0.18em;
width: 40%;
height: 20%;
-webkit-box-shadow: 0px 13px 8px #979797;
-moz-box-shadow: 0px 13px 8px #979797;
box-shadow: 0px 13px 8px #979797;
-webkit-transform: rotate(-2deg);
-moz-transform: rotate(-2deg);
-ms-transform: rotate(-2deg);
-o-transform: rotate(-2deg);
transform: rotate(-2deg);
}

:not(pre) > code[class*="language-"]:after,
pre[class*="language-"]:after {
right: 0.75em;
left: auto;
-webkit-transform: rotate(2deg);
-moz-transform: rotate(2deg);
-ms-transform: rotate(2deg);
-o-transform: rotate(2deg);
transform: rotate(2deg);
}

.token.comment,
.token.block-comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: #7D8B99;
}

.token.punctuation {
color: #5F6364;
}

.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.function-name,
.token.constant,
.token.symbol,
.token.deleted {
color: #c92c2c;
}

.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.function,
.token.builtin,
.token.inserted {
color: #2f9c0a;
}

.token.operator,
.token.entity,
.token.url,
.token.variable {
color: #a67f59;
background: rgba(255, 255, 255, 0.5);
}

.token.atrule,
.token.attr-value,
.token.keyword,
.token.class-name {
color: #1990b8;
}

.token.regex,
.token.important {
color: #e90;
}

.language-css .token.string,
.style .token.string {
color: #a67f59;
background: rgba(255, 255, 255, 0.5);
}

.token.important {
font-weight: normal;
}

.token.entity {
cursor: help;
}

.namespace {
opacity: .7;
}

@media screen and (max-width: 767px) {
pre[class*="language-"]:before,
pre[class*="language-"]:after {
bottom: 14px;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
}

}

/* Plugin styles */
.token.tab:not(:empty):before,
.token.cr:before,
.token.lf:before {
color: #e0d7d1;
}

/* Plugin styles: Line Numbers */
pre[class*="language-"].line-numbers {
padding-left: 0;
}

pre[class*="language-"].line-numbers code {
padding-left: 3.8em;
}

pre[class*="language-"].line-numbers .line-numbers-rows {
left: 0;
}
-->

Effective C++ Notes Part II

Part II. Constructors, Destructors and Assignment Operators

5. Know what functions C++ silently writes and calls. (C++ 03)

  • If any of default constructor, default destructor, copy constructor or assignment operator is not defined manually, compiler will provide a default public inline version of it. Particularlly, the compiler provided destructor is a non-virtual version unless its base class contains a virtual destructor and both copy constructor and assignment operator simply copy every non-static member variables. But if compiler generated code is illegal or nonsense,the compiler will refuse to generate such codes.

    A class is defined as follow:

    template<class T>
    class NamedObject
    {
    public:
        NamedObject(std::string& name, const T& value);
    private:
        std::string& nameValue;
        const T objectValue;
    };
    

    Then we use it like this:

    std::string newDog("A");
    std::string oldDog("B");
    NamedObject<int> p(newDog, 2);
    NamedObject<int> s(oldDog, 36);
    p = s;
    

    we know that reference can‘t be re-directed and const members can‘t be changed. So the compiler will refuse to compile the line ‘p=s‘

6. Explicity disallow the use of compiler-generated functions you do not want

  • In some cases a class should be disallowd to be copied, but if you don‘t provide a copy constructor nor a assignment operator for it, the compiler would do, thus the class would become copyable. We can disable such operations via define copy constructor and assignment operator as private but this is not absolutely safe since member functions and friend class/functions still can call them, otherwise we will throw the problem to linker via undefined copy operations. But, a compile-time error is far more better than link-time error, a base class with private copy operations will do that.

    class NonCopyable
    {
        public:
            NonCopyable() {}
            virtual ~NonCopyable() {}
        private:
            NonCopyable(const NonCopyable&);
            NonCopyable& operator=(const NonCopyable&);
    };
    
    class MyClass : private NonCopyable
    {
    ...
    };
    

    Let our class inherit this NonCopyable class, copy operations in derived class will try to call such operations in base class those are declared private which the compiler won‘t let happen and we got a compile-time error, exactly what we want.

7. Declare destructors virtual in polymorphic base classes

  • There is a implicit trap in polymorphic structures, if we don‘t declare the destructor of base class virtual, something unexpected may happen.

    class A
    {
        A() : a_(new int(1)) {}
        ~A() { delete a_; }
        int *a_;
    };
    
    class B : public A
    {
        B() : A(), b_(new int(2)) {}
        ~B() { delete b_; }
        int *b_;
    };
    

    if we use these class like this

    B *pb = new B();
    A *pa = pb;
    delete pa;
    

    It is OK in syntax because B is inherited from A, but the delete statement actually called ~A() other than ~B(), thus b_ in class B is not released which leads to the memory leak. To deal with such situation, we simply need to declare ~A() as virtual.

    • If a class possesses any virtual, whose destructor should be declared virtual since a virtual class must be designed to act as a base class, a base class need its destructor to be virtual in order to avoid partly released resources and potential resource leak.
    • A class with no virutal method still may be used as base class, whenever a class is used as base class in polymorphic structure, it needs a virtual desctructor. Inherit a class with non-virtual destructor, such as std::string may cause unspecified behaviors.
    • Declare destructor of a class which won‘t be derived virtual is not wise, if any method of a class is declared virtual, compiler will provide a vtable pointer to look up the vtable for the class which need sizeof(void*) bytes of memory. This will result in the loss of compatitance with C since the instance won‘t have a plain memory.
  • It is convenient to declare a abstruct class by declaring a pure virtual method, Thus the class is designed to be used as base class so it need a virtual destructor. But in a polymorphic structure, the desctructor of the derived class is called and then calls the base class destructor. A pure virtual destructor here may cause link error so we need to provide a definition for it.

8. Prevent exceptions from leaving destructors

  • In containers or arrays storing user-defined types, an uncatched exception in the descturctor of such type may be throwed multiple times and results in undefined behaviors.
  • If destructor of a class need to call a function which may throw exceptions, RAII classes as an example, if the function runs well then everything is on the track, but if the function fails, it may throw a exception.
    class DBConnection
    {
        public:
            static DBConnection create();
            void close();
    };
    
    //We will adopt RAII to manage the DBConnection
    
    class DBConn
    {
        public:
            ...
            ~DBConn() { db.close(); }
        private:
            DBConnection db;
    };
    

    To avoid such problem, we can just kill the process itself via abort()

    DBConn::~DBConn()
    {
        try { db.close(); }
        catch(...)
        {
            Log the exception
            std::abort();
        }
    }
    

    It‘s reasonable to kill a process if the control flow can‘t be continued if any exception is throw in destruction. This may prevent the exception from throwing to the outer block which may cause undefined behaviors.

    Or we can just Log the exception and let the process continue

    DBConn::~DBConn
    {
        try { db.close(); }
        catch(...)
        {
            Log the exception
        }
    }
    

    It is not wise to ignore an exception in most circumstances because this may hide the information about a serious error, but if an error is not serious and ignorance of which won‘t trigger any serious effect then it is applicable to ignore such exceptions.

    Both approaches above are not perfect for not providing the users a chance to handle the exceptions, in order to do so, we can redesign the DBConn class as below.

    class DBConn {
        public:
            ...
            void close() { db.close(); }
            ~DBConn()
            {
                if(!closed)
                {
                    try { db.close(); }
                    catch(...) {
                        Log the exception then abort the process or just ignore it
                    }
                }
            }
        private:
            DBConnection db;
            bool closed;
    };
    

    The close() method provides our user an approch to handle the exception in DBConnection::close() and we still provide a failsafe in destructor. This design won‘t add complexity to the interface. An exception in destruction is very dangerous as we can hardly handle the exception correct in the destructor with no understanding of outside business logic.

9. Never call virtual functions during construction or destruction

  • Consider the code below

    class Base
    {
        public:
            Base();
            virtual foo() const = 0;
    };
    
    Base::Base()
    {
        foo();
    }
    
    class Derived : public Base
    {
        public:
            virtual foo() const;
    }
    
    Derived instance;
    

    As we know, when we instancize a derived object, Base() is called first followed by Derived(). But before ~Derived() is called, the type of the constructing object is Base instead of Derived, thus the Base constructor would call the Base::foo() instead of Derived::foo() which is obviously not what we want. In this example foo() is pure so we would get a link error, but if the base class provide a default definition of foo(), we may have a compile time warning if we are lucky, otherwise we will spend lots of time on debugging.

  • It is the same when it comes to destructor, once ~Derived() is called, the derived members become undefined, When ~Base() is called, the type of such object is considered Base.
  • There are other hidden traps when the Base class contains several overloaded constructor, it is very common to provide an initializer to avoid duplicating codes and the initializer calls a virtual function.
    class Base
    {
        public:
            Base() { init(); }
            virtual void foo() const = 0;
        private:
            void init() { foo(); }
    };
    

    This mistake is hidden deeper than previous and is hard to notice it. To avoid been trapped, we must ensure that no virtual functions are called in both constructor and destructor explicitly.

  • There are other approaches, one among them is modify the virtual function to non-virtual and pass the information needed to the base constructor.
    class Base
    {
        public:
            explicit Base(const std::string& logInfo)
            {   Log(logInfo); }
            void Log(const std::string& logInfo);
    };
    
    class Derived : public Base
    {
        public:
            Derived(parameter)
                : Base(makeString(parameter))
            { ... }
    }
    

    As we explained above that virtual functions can‘t be pass down along the inheritance link but we can pass the required constructing info upside to base constructor.

10. Have assignment operators return a reference to *this

  • For built-in types and STL types we can write assignment in a link like this

    int x, y, z;
    x = y = z = 1;
    

    if we disassamble this expression, it will be

    x.operator= ( y.operator= (z) )
    

    To achieve this in custom types, we need define the opertor= like this

    class Widget
    {
        public:
            Widget& operator=(const Widget& rhs)
            {
                ... copy the members ...
                return *this;
            }
    }
    

    This rule is not applicable to operator= but also all assignment operators such as +=, -=, *=, etc. Even the parameter doesn‘t share the same type with return value, the rule should still be adopted.

    Widget& Widget::operator+=(int rhs)
    {
        //do the copy
        return *this;
    }
    

    This rule is not forced to be obeyed but it‘s adopted by all built-in types and STL types, even just in consideration of STL compatibility we should obey it.

  • In my opinion assignment operator should be defined to return a const reference to *this in order to avoid such operations
    object a,b,c;
    (a=b)=c;
    

11. Handle assignment to self in opertor=

  • Self Assignment happens if an object calls operator= with itself as parameter, aliases such as references and pointers offten cause self assignments. Assignment of a class may result in catastrophy if self assignment is not correctly handled.

    class Bitmap{...}
    class Widget
    {
        ...
        Bitmap *bp;
    };
    
    Widget& Widget::operator=(const Widget& rhs)
    {
        delete bp;
        bp = new Bitmap(*rhs.bp);
        return *this;
    }
    

    If we call operator of an Widget object with itself as parameter, the Bitmap object hold by the Widget is freed, this is absolutely not what we want. An equality check just after entering operator= may solve the problem

    Widget& Widget::operator=(const Widget& rhs)
    {
        if (this == rhs) return *this;
        delete bp;
        bp = new Bitmap(*rhs.bp);
        return *this;
    }
    

    This approach is self assignment safe but still not exception safe, if an exception throw in construction of new Bitmap, we still get an empty Bitmap pointer which we can‘t read them nor delete them. A exception safe assignment operator is offten also self assignment safe and all we need to do is to save a copy of bp before we successfully copied the exact resource.

    Widget& Widget::operator=(const Widget& rhs)
    {
        Bitmap* pOrig = bp;
        bp = new Bitmap(*rhs.bp);
        delete pOrig;
        return *this;
    }
    

    With efficiency concerns on high self assignment frequency, we may add the identity test back to the beginning of operator= which may cause a bigger compiled target and tiny or negtive uptick on performance depends on self assignment frequency.

    Another approach to implement a self assignment and exception safe operator= is called copy and swap, first we get a copy of rhs and then we swap it with *this.

    Widget& Widget::operator=(const Widget& rhs)
    {
        Widget tmp(rhs);
        swap(tmp);
        return *this;
    }
    //An simpler but not very clear approach
    Widget& Widget::operator=(Widget rhs) //The parameter is passed by value(copy)
    {
        swap(rhs);
        return *this;
    }
    

12. Copy all parts of an object

  • An implementation of copy operation which copies only some part of the object is offten considered to be OK by most compilers, which means you won‘t get an error nor a warning on it.

    class Date {...};
    class Customer
    {
        public:
            Customer(const Customer& rhs);
            Customer& operator=(const Customers& rhs);
            ...
        private:
        I   std::string name_;
    };
    
    Customer::Customer(const Customer& rhs) : name_(rhs.name_) {}
    Customer& Customer::operator=(const Customer& rhs)
    {
        //deal with self assignments
        name_ = rhs.name_;
        return *this;
    }
    

    It seems that everything is OK here and they are OK indeed until class Customer‘s private members become

    class Customer
    {
        //Same with Customer above
        private:
            std::string name_;
            Date lastTransaction_;
    };
    

    Now the copy functions just copy half of a Customer‘s members and the compiler doesn‘t take this kind of partial copy as an error. It is obvious that if we change a class definition, we need to revise the copy functions for it to match the revised class.

  • When it comes to a inheritance structure, traps are hiden more deeper.
    class Vip : public Customer
    {
        public:
            Vip(const Vip& rhs);
            Vip& operator=(const Vip& rhs);
        private:
            int level_;
    };
    
    Vip::Vip(const Vip& rhs) : level_(rhs.level_) {}
    Vip& Vip::operator=(const Vip& rhs)
    {
        level_ = rhs.level_;
        return *this;
    }
    

    An Vip instance will contains two parts, one is level_ and the other is a copy of a Customer object. It‘s not wisdom to count on the compiler to take care of the Customer object just because it is not explicit showed on screen. Compiler won‘t take the job nor it will warns you the existance of the objectm, so we must do this carefully in avoidance of some unexpected behaviors. And you can‘t get private members from the base class object, so a Base copy constructor is needed.

    Vip::Vip(const Vip& rhs)
    : Customer(rhs),
      level_(rhs.level_)
    {
    }
    
    Vip& Vip::operator=(const Vip& rhs)
    {
        Customer::operator=(rhs);
        level_ = rhs.level_;
        return *this;
    }
    
  • The copy constructor and assignment operator offten have similar implementations, but it‘s irreasonable to implement one by calling another
    • Let copy assignment operator call copy constructor is meaningless. WHat would happen if we try to construct an existing object?
    • Let copy construct call copy assignment operator is also meaningless. The copy constructor is designed to construct a new object which contains the entry of copy assignment operator. Calling the copy assignment operator during construction, which means the target object is not really exist, is meaningless.
    • To avoid code duplication, one appropriate approach is to provide a new private member function called init()

Conclusion:

5.1 Compilers may implicitly generate a class‘s default constructor, copy constructor, copy assignment operator and destructor

6.1 To disallow functionality automatically provided by compilers, declare the corresponding member functions private and give no implementations. Using a base class like Uncopyable is one way to do this.

7.1 Polymorphic base classes should declare virtual destructors. If a class has any virtual functions, it should have a virtual destructor.

7.2 Classes not designed to be base classes or not designed to be used polymorphically should not declare virtual destructors

8.1 Destructors should never emit exception. If functions called in a destructor may throw, the destructor should catch ant exceptions, then swallow them or terminate the program.

8.2 If class clients need to be able to react to exceptions thrown during an operation, the class should provide a regular(i.e., non-destructor) function that performs the operation.

9.1 Don‘t call virtual functions during construction or destruction, beacuse such calls will never go to a more derived class than that of the currently executing constructor or destructor.

10.1 Have assignment operators return a reference to *this.

11.1 Make sure operator= is well-behaved when an object is assigned to itself. Techniques include comparing addresses of source and target object, careful statement ordering, and copy-and-swap.

11.2 Make sure that any function operating on more than one object behaves correctly if two or more of the objects are the same.

12.1 Copying functions should be sure to copy all of an object‘s data members and all of its base class parts.

12.2 Don‘t try to implement one of the copying functions in terms of the other. Instead, put common functionality in a third function that both call.

时间: 2024-10-10 04:16:56

Effective C++读书笔记 Part2的相关文章

Effective Objective-C 读书笔记

一本不错的书,给出了52条建议来优化程序的性能,对初学者有不错的指导作用,但是对高级阶段的程序员可能帮助不是很大.这里贴出部分笔记: 第2条: 使用#improt导入头文件会把头文件的内容全部暴露到目标文件中,而且如果两个类之间存在循环引用则会出现编译错误,所以要尽量使用@class进行类声明. 如果需要实现一个协议,则必须#improt这个协议的头文件,所以可以将协议单独定义在一个.h文件当中.如果这个协议是代理模式协议的一部分,即需要与类捆绑使用才有实际意义,则建议定义在类当中,并以类名为前

Effective Java 读书笔记(2创建和销毁对象)

第一章是引言,所以这里不做笔记,总结一下书中第一章的主要内容是向我们解释了这本书所做的事情:指导Java程序员如何编写出清晰.正确.可用.健壮.灵活和可维护的程序. 2.1考虑用静态工厂方法代替构造器 静态工厂方法与构造器相比有四大优势: (1)静态工厂方法有名称,具有适当名称的静态工厂方法易于使用.易于阅读: (2)不必每次在调用它们的时候都创建一个新的对象: (3)可以返回原返回类型的任何子类型的对象: (4)在创建参数化类型实例的时候,它们使代码变得更加简洁. 同时静态工厂方法也有两大缺点

Effective Java读书笔记(4 类和接口)

4.1 使类和成员的可访问性最小化 要区别设计良好的模块和设计不好的模块,最重要的因素在于,这个模块对于外部的其他模块而言,是否隐藏其内部数据和其他实现细节.设计良好的模块会隐藏所有的实现细节,把它的API与它的实现清晰的隔离开来,然后模块之间只通过API进行通信,一个模块不需要知道其他模块内部的工作情况,这个概念被称为信息隐藏或封装,是软件设计的基本原则之一. 4.2 在公有类中使用访问方法而非公有域 坚持面向对象程序设计思想:如果类可以在它所在的包的外部进行访问,就提供访问方法,以保留将来改

Effective Java读书笔记(3对于所有对象都通用的方法)

3.1 覆盖equals时请遵守通用约定 什么时候应该覆盖Object.equals()方法呢? 如果类具有自己特有的"逻辑相等"概念(不同于对象等同的概念),而且超类还没有覆盖equals以实现期望的行为,这时我们就需要覆盖equals方法. Object.equals()方法具有自反性.对称性.传递性.一致性和与null比较返回false的特点. 实现高质量equals方法的诀窍: (1)使用==操作符检查"参数是否为这个对象的引用".如果是,则返回true,这

Effective C++读书笔记之十二:复制对象时勿忘其每一个成分

Item 12:Copy all parts of an object 如果你声明自己的copying函数,意思就是告诉编译器你并不喜欢缺省显示中的某些行为.而编译器会对"你自己写出copying函数"做出一种复仇的行为:既然你拒绝它们为你写出copying函数,如果你的代码不完全,它们也不会告诉你.结论很明显:如果你为class添加一个成员变量,你必须同时修改copying函数.如果你忘记,编译器不太可能提醒你. 一下提供一种正确的模版: class Date{...}; class

Effective C++读书笔记之十三:以对象管理资源

Item 13:Use objects to manage resources 假设我们使用一个用来塑膜投资行为的程序库,其中各式各样的投资类型继承自一个root class: class Investment { ... };  //"投资类型"继承体系中的root class 进一步假设,这个程序系通过一个工厂函数(工厂函数会"返回一个base class指针,指向新生成的derived class 对象),供应我们某特定的Investment对象: Investment

&lt;Effective Django&gt;读书笔记

In Django parlance, a project is the final product, and it assembles one or more applications together. Manage.py is a pointer back to Django-admin.py with an environment variable set, pointing to your project as the one to read settings from and ope

《Effective C++ 读书笔记》( 一 )

<Effective C++ 读书笔记> 条款01 : 视C++为一个语言联邦 将C++ 视为一个由相关语言组成的联邦而非单一语言. 我们可以将C ++ 看成四个部分 : 1. C 语言 . C ++ 中的区块 , 语句, 预处理器 , 内置数据类型 , 数组 , 指针等统统来自于C语言. 2. 面向对象的 C ++ . 类( 包括构造函数和析构函数 ) , 封装 , 继承 ,多态 , virtual 函数 ( 动态绑定 ) ... 等等 . 这一部分都是基于面向对象而设计的. 3. Temp

effective C++ 读书笔记 条款06

条款06:若不想使用编译器自动生成的函数,就该明确拒绝: 直接看代码与注释: #include <iostream> using namespace std; class Test { public: Test() { } ~Test() { } /* void TT() { Test t1; Test t2(t1); } */ private: Test(const Test& test); Test& operator = (const Test& test); }