"Effective C + +": Clause 31: Minimizing compilation dependencies between files

Source: Internet
Author: User

    If you are modifying the program, just modify the implementation of the interface of a class, and modify the private part. Later, when you compile, you find that many files have been recompiled. The problem is that it does not "detach the interface from the implementation." The definition of class does not only describe the class interface in detail, but also includes a number of implementation details:

        classperson{ Public: Person (Const STD::string& Name,Constdate& Birthday,Constaddress& addr);STD::stringName ()Const;STD::stringBirthDate ()Const;STD::stringAddress ()Const; ......Private://Implementation Breakdown        STD::stringthename;        Date the BirthDate;    Address theaddress; };

    To compile, include the string, Date, and address used in class. At the Person front of the definition file, there should be:

        #include<string>    #include"date.h"    #include"address.h"

    Personthis creates a compilation dependency between the definition file and its included files (compilation dependency). If these header files have a modification, the files that use the person class are recompiled. Such a tandem compilation dependency (cascading compliation dependencies) can cause a lot of inconvenience to the project.
    So why is C + + putting class implementation details in class definition? You can separate the implementation breakdown:

        namespacestd{        classstring;//前置声明    }    class Date;//前置声明    class Address;//前置声明    class Person{    public:    ……    };

    The first step is not to discuss whether the pre-declaration is correct (actually wrong), and if you can do so, the person's customer only needs to recompile when the person interface is modified. But there are two problems with this idea.
    -1, string is not a class, it is a TypeDef, defined as basic_string. The former declaration of the string is incorrect, and the correct predecessor declaration is more complex because it involves additional templates. In fact, we should not declare a standard library, using # include. Standard header files generally do not become a compilation bottleneck, especially if you allow precompiled header files (precompiled headers) to be used in your build environment. If the parsing (parsing) standard header file is a problem, the general situation is that you need to modify your interface design.
    -2, the predecessor declaration of everything, the compiler must know the size of the object during compilation. For example

    int main()    {        int x;        params);        ……    }

    When the compiler sees the X-definition, you must know how much memory is allocated to X, and then when the compiler sees the definition of p, you should know how much memory must be allocated to P. If the definition of class does not list implementation details, the compiler cannot know how much space is allocated to P.

    This problem does not exist in languages such as Java, because when they are defining an object, the compiler simply assigns a pointer (to point to the object). The above code implementation looks like this:

    int main()    {        int x;        Person* p;//定义一个指向Person的指针        ……    }

    In C + +, you can also do this by hiding the object implementation details behind a pointer. For person, it can be divided into two classes, one is responsible for providing the interface, the other is responsible for implementing the interface. The interface responsible for implementation is named Personimpl (person Implementation):

        #include <string>    #include <memory>    classPersonimpl;//front-facing statement    classDate;classAddress;classperson{ Public: Person (Const STD::string& Name,Constdate& Birthday,Constaddress& addr);STD::stringName ()Const;STD::stringBirthDate ()Const;STD::stringAddress ()Const; ......Private://Implementation Breakdown        STD:: TR1::shared_ptr<PersonImpl> Pimpl;//pointer, pointing to implementation};

    This design is called Pimpl idiom (pimpl:pointer to implementation). The customer of person is separated from the implementation breakdown of date, address, and person. Any implementation modification of classes does not have to be recompiled by the client. In addition, the customer is not able to see the details of the person's implementation, and will not write "depending on the details of the code", the real implementation of "interface and implementation separation."

    The key to this separation is that "declarative dependency" replaces "defined dependencies", which is the essence of minimizing compilation dependencies: In reality, make the header file as self-fulfilling as possible, and if not, let it depend on the declaration (not the definition) in the other files. Others are derived from the following design strategies:

      • If you use object references or object pointers to complete a task, do not use object. Because using references or pointers requires only one declaration, the definition objects need to use that type of definition.
      • if possible, replace the class definition with a class declaration . However, when declaring a function using a class, the class definition is not required even if the type parameter/return value is passed by value. However, when using these functions, these classes must be exposed before calling the function. The customer is ultimately going to know the definition of classes, but the implication is that the obligation to provide a class definition (through # include) is transferred from the header file where the function declaration is to the client file of the function call.
      • provides two different header files for both declarative and defined types. In the program, the customer should not be given a predecessor declaration, the program author generally provides two header files, one for the declarative, one for the definition of the formula. In the header file of the C + + standard library (terms and conditions), the declarations of the iostream components are included, and <iosfwd> their corresponding definitions are distributed in different file parts, including,,, <sstream> <streambuf> <fstream> <iostream> .

    <iosfwd>Note, the same applies to templates and non-templates. As mentioned in clause 30, the template is usually defined in the header file, but there are also some built environments that allow the template to be defined in a non-header file, so that the "declarative only" header file can be provided to templates. <iosfwd>is such a file.

    The keyword export is provided in C + + to split template declarations and definitions within different files. However, there are not many compilers that support the Export keyword.

    The classes, which uses Pimpl idiom like person, is called handle classes. One of the ways in which this class really works is to pass all their functions to the corresponding implementation class (Implementation classes), which is actually done by the implementation class. For example, the person's implementation:

        #include"Person.h"    #include"PersonImpl.h"    Person::Person(const std::string&Date& birthday, const Address& addr):            pImpl(new PersonImpl(name, birthday, addr)){}    std::string Person::name() const    {        return pImpl->name();    }    ……

    In Personimpl, there is a member function that is exactly the same as the person, and the interface is identical.

    Another way to implement handle class is to make person a special abstract base class, called Interface class. Such class member variables simply describe the derived classes interface ( clause 34), there is no constructor, there is only one virtual destructor ( clause 7) and this set of pure virtual functions, Used to describe the entire interface.

    Interface classes is similar to Java and. NET Interface, but the C + + Interface class differs from Interface in Java and. NET, allowing variables to be more resilient. As stated in clause 36, the "Implementation of the Non-virtual function" should be the same for all classes in the inheritance system. It is also reasonable to implement such a function as part of the interface class, where a corresponding declaration is written.

    Interface class such as person can write these:

     class  person{public : virtual  ~person (); virtual  std :: string  name ()        const  =0 ; virtual  std :: string  birthDate ()        const  =0 ; virtual  std :: string  address ()        const  =0 ; ...};  

    This class's client must use the person's pointers or references, because the class containing the pure virtual function cannot be instantiated. This way, as long as the interface class interface is not modified, other customers do not need to recompile.

    Interface class customers typically use a special function when creating new objects for class, which plays the role of the derived classes constructor, which is really going to be materialized. Such functions are called Engineering functions Factory ( clauses) or virtual constructors, which return pointers (more likely to be smart pointers, * * clause **18), and point to dynamically allocated objects that support the interface class interface. The factory function is typically declared as static

    class Person{    public:        ……        std::tr1::shared_ptr<Person create(……)        ……    };

    Customers use this

        std::string name;    Date dateOfBirth;    Address address;    std::tr1::shared_ptr<Person> pp(Person::create(……));    std::cout<<pp->name()<<"was born"<<pp->birthDate()<<"and now lives at"<<pp->address();

    The specific class that supports the interface class interface (concrete classes) is defined before a real constructor call. For example, a realperson inherits the person

     class Realperson: public  person {public : realperson  (const  std::string  & name, const  date& birthday, const  address& addr): thenam e  (name), thebirthdate  (Birthday), theaddress         (addr) {} virtual  ~realperson () {};        Std::string  name () const ;    ......        private : Std::string  thename;        Date thebirthdate;    Address theaddress; };

    With Realperson, you can write Person::create.

    std::tr1::shared_ptr<Person>Person::std::string&DateAddress& addr){    returnstd::tr1::shared_ptr<Person>RealPerson(name, birthday, addr));    }

    The mechanism by which Realperson implements interface class is to inherit the interface from the interface class and then implement the functions covered by the interface. There is also an implementation method, design multiple inheritance, in the * * clause **40 discussion.

    Handle classes and Interface classes remove the coupling between the interface and the implementation, thus reducing compilation dependencies. But there are some costs to it: you lose some of the speed during the run, and you open up more memory than the object.

    On handle classes, the member function obtains object data through implementation pointer. This adds a layer of indirection to the access, and the memory also increases the size of the implementation pointer. The initialization of implementation pointer, with the additional overhead of dynamically opening up memory, suffers the possibility of encountering bad_alloc anomalies.

    In interface classes, each function is virtual, so each call has to pay an indirect leap (indirect jump) cost. The derived object will have a vptr (virtual table pointer,** clause **7), which increases the memory required for the object.

    Handle classes and interface classes, once out of the inline function, cannot be too much. * * Clause **30 explains why inline functions are placed in the header file, but handle classes and interface classes are designed to hide implementation details.

    What we want to do is to use handle classes and interface classes in the program in order to have the least impact on their customers when the code changes. However, if they result in excessive extra costs, such as causing a run speed or an object size difference that is so large that the coupling between classes is not critical, replace handle classes and interface with a concrete class (concrete classes) Classes

    Summary
    -The general idea of supporting the minimization of compile dependencies is that it depends on the declarative, not on the definition. The two instruments based on this conception are handle classes and interface classes.
    -The library header file should be in the form of "complete and declarative Only" (Full and Declaration-only forms). Whether or not these templates, this practice is applicable.

    "Effective C + +": Clause 31: Minimizing compilation dependencies between files

    Contact Us

    The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

    If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.