<Effective C++>读书摘要--Inheritance and Object-Oriented Design<二>

<Item 36> Never redefine an inherited non-virtual function

1、如下代码通过不同指针调用同一个对象的同一个函数会产生不同的行为The reason for this two-faced behavior is that non-virtual functions like B::mf and D::mf are statically bound (see Item 37). That means that because pB is declared to be of type pointer-to-B, non-virtual functions invoked through pB will always be those defined for class B, even if pB points to an object of a class derived from B, as it does in this example.

Virtual functions, on the other hand, are dynamically bound (again, seeItem 37), so they don't suffer from this problem. If mf were a virtual function, a call to mf through either pB or pD would result in an invocation of D::mf, because what pB and pD really point to is an object of type D. 

class B {
public:
  void mf();
  ...
};
class D: public B {
public:
  void mf();                      // hides B::mf; see Item33
  ...
};

D x;
// x is an object of type D B *pB = &x; // get pointer to x pB->mf(); // calls B::mf D *pD = &x; // get pointer to x pD->mf(); // calls D::mf

 

 2、If reading this Item gives you a sense of déjà vu, it's probably because you've already read Item 7, which explains why destructors in polymorphic base classes should be virtual. If you violate that guideline (i.e., if you declare a non-virtual destructor in a polymorphic base class), you'll also be violating this guideline, because derived classes would invariably redefine an inherited non-virtual function: the base class's destructor. This would be true even for derived classes that declare no destructor, because, as Item 5 explains, the destructor is one of the member functions that compilers generate for you if you don't declare one yourself. In essence, Item 7 is nothing more than a special case of this Item, though it's important enough to merit calling out on its own.

3、Things to Remember

  • Never redefine an inherited non-virtual function. 

<Item 37> Never redefine a function's inherited default parameter value

4、virtual functions are dynamically bound, but default parameter values are statically bound.(For the record, static binding is also known as early binding, and dynamic binding is also known as late binding.) 

5、An object's static type is the type you declare it to have in the program text.An object's dynamic type is determined by the type of the object to which it currently refers. Dynamic types, as their name suggests, can change as a program runs, typically through assignments.

6、Why does C++ insist on acting in this perverse manner? The answer has to do with runtime efficiency. If default parameter values were dynamically bound, compilers would have to come up with a way to determine the appropriate default value(s) for parameters of virtual functions at runtime, which would be slower and more complicated than the current mechanism of determining them during compilation. The decision was made to err on the side of speed and simplicity of implementation, and the result is that you now enjoy execution behavior that is efficient, but, if you fail to heed the advice of this Item, confusing.默认参数只是语法糖,在编译期已经展开

7、When you're having trouble making a virtual function behave the way you'd like, it's wise to consider alternative designs, and Item 35 is filled with alternatives to virtual functions. One of the alternatives is the non-virtual interface idiom (NVI idiom): having a public non-virtual function in a base class call a private virtual function that derived classes may redefine. Here, we have the non-virtual function specify the default parameter, while the virtual function does the actual work。

例如下面的代码存在代码重复和而且存在代码依赖,Shape 和Rectangle的draw函数默认参数必须一致,产生了依赖

class Shape {
public:
  enum ShapeColor { Red, Green, Blue };
  virtual void draw(ShapeColor color = Red) const = 0;
  ...
};
class Rectangle: public Shape {
public:
  virtual void draw(ShapeColor color = Red) const;
  ...
};

 

可以做如下修改

class Shape {
public:
  enum ShapeColor { Red, Green, Blue };
  void draw(ShapeColor color = Red) const           // now non-virtual
  {
    doDraw(color);                                  // calls a virtual
  }
  ...
private:
  virtual void doDraw(ShapeColor color) const = 0;  // the actual work is
};                                                  // done in this func
class Rectangle: public Shape {
public:
  ...
private:
  virtual void doDraw(ShapeColor color) const;       // note lack of a
  ...                                                // default param val.
};

 

Because non-virtual functions should never be overridden by derived classes (see Item 36), this design makes clear that the default value for draw's color parameter should always be Red.

8、Things to Remember

  • Never redefine an inherited default parameter value, because default parameter values are statically bound, while virtual functions — the only functions you should be overriding — are dynamically bound.

<Item 38> Model "has-a" or "is-implemented-in-terms-of" through composition

9、Composition is the relationship between types that arises when objects of one type contain objects of another type. Among programmers, the term composition has lots of synonyms. It's also known as layering, containment, aggregation, and embedding.

10、Item 32 explains that public inheritance means "is-a." Composition has a meaning, too. Actually, it has two meanings. Composition means either "has-a" or "is-implemented-in-terms-of." That's because you are dealing with two different domains in your software. Some objects in your programs correspond to things in the world you are modeling, e.g., people, vehicles, video frames, etc. Such objects are part of the application domain. Other objects are purely implementation artifacts, e.g., buffers, mutexes, search trees, etc. These kinds of objects correspond to your software's implementation domain. When composition occurs between objects in the application domain, it expresses a has-a relationship. When it occurs in the implementation domain, it expresses an is-implemented-in-terms-of relationship.

12、set implementations typically incur an overhead of three pointers per element. This is because sets are usually implemented as balanced search trees, something that allows them to guarantee logarithmic-time lookups, insertions, and erasures. When speed is more important than space, this is a reasonable design, but it turns out that for your application, space is more important than speed. The standard library's set thus offers the wrong trade-off for you. It seems you'll need to write your own template after all.

13、Things to Remember

  • Composition has meanings completely different from that of public inheritance.

  • In the application domain, composition means has-a. In the implementation domain, it means is-implemented-in-terms-of.

<Item 39> Use private inheritance judiciously

14、Item 32 demonstrates that C++ treats public inheritance as an is-a relationship. It does this by showing that compilers, when given a hierarchy in which a class Student publicly inherits from a class Person, implicitly convert Students to Persons when that is necessary for a function call to succeed. It's worth repeating a portion of that example using private inheritance instead of public inheritance: 

class Person { ... };
class Student: private Person { ... };     // inheritance is now private 私有继承的时候private可以省略
void eat(const Person& p);                 // anyone can eat
void study(const Student& s);              // only students study
Person p;                                  // p is a Person
Student s;                                 // s is a Student
eat(p);                                    // fine, p is a Person
eat(s);                                    // error! a Student isn't a Person

 15、 the first rule governing private inheritance you've just seen in action: in contrast to public inheritance, compilers will generally not convert a derived class object (such as Student) into a base class object (such as Person) if the inheritance relationship between the classes is private. That's why the call to eat fails for the object s. The second rule is that members inherited from a private base class become private members of the derived class, even if they were protected or public in the base class.

16、So much for behavior. That brings us to meaning. Private inheritance means is-implemented-in-terms-of. If you make a class D privately inherit from a class B, you do so because you are interested in taking advantage of some of the features available in class B, not because there is any conceptual relationship between objects of types B and D. As such, private inheritance is purely an implementation technique. (That's why everything you inherit from a private base class becomes private in your class: it's all just implementation detail.) Using the terms introduced in Item 34, private inheritance means that implementation only should be inherited; interface should be ignored. If D privately inherits from B, it means that D objects are implemented in terms of B objects, nothing more. Private inheritance means nothing during software design, only during software implementation.

17、The fact that private inheritance means is-implemented-in-terms-of is a little disturbing, because Item 38 points out that composition can mean the same thing. How are you supposed to choose between them? The answer is simple: use composition whenever you can, and use private inheritance whenever you must. When must you? Primarily when protected members and/or virtual functions enter the picture, though there's also an edge case where space concerns can tip the scales toward private inheritance. We'll worry about the edge case later. After all, it's an edge case.

18、定时器是私有继承的典型应用,在各种钩子和回调函数中也经常使用私有继承

class Timer {
public:
  explicit Timer(int tickFrequency);
   virtual void onTick() const;          // automatically called for each tick
  ...
};
class Widget: private Timer {
private:
  virtual void onTick() const;           // look at Widget usage data, etc.
  ...
};

也可以使用显示的组合来实现 

class Widget {
private:
  class WidgetTimer: public Timer {
  public:
    virtual void onTick() const;
    ...
  };
   WidgetTimer timer;
  ...
};

 组合相比私有继承有两个好处First, you might want to design Widget to allow for derived classes, but you might also want to prevent derived classes from redefining onTick. If Widget inherits from Timer, that's not possible, not even if the inheritance is private. (Recall from Item 35 that derived classes may redefine virtual functions even if they are not permitted to call them.) .Second, you might want to minimize Widget's compilation dependencies.可以通过pimp的形式减少编译依赖

19、I remarked earlier that private inheritance is useful primarily when a would-be derived class wants access to the protected parts of a would-be base class or would like to redefine one or more of its virtual functions, but the conceptual relationship between the classes is is-implemented-in-terms-of instead of is-a. However, I also said that there was an edge case involving space optimization that could nudge you to prefer private inheritance over composition.

class Empty {};                      // has no data( no non-static data members; 
no virtual functions (because the existence of such functions adds a vptr to each object — see Item 7);
and no virtual base classes (because such base classes also incur a size overhead — see Item 40). )
// so objects should use no memory class HoldsAnInt { // should need only space for an int private: int x; Empty e; // should require no memory };

 

 you'll find that sizeof(HoldsAnInt) > sizeof(int); an Empty data member requires memory. With most compilers, sizeof(Empty) is 1, because C++'s edict against zero-size freestanding objects is typically satisfied by the silent insertion of a char into "empty" objects. However, alignment requirements (see Item 50) may cause compilers to add padding to classes like HoldsAnInt, so it's likely that HoldsAnInt objects wouldn't gain just the size of a char, they would actually enlarge enough to hold a second int. (On all the compilers I tested, that's exactly what happened.) 

class HoldsAnInt: private Empty {
private:
  int x;
};

 

you're almost sure to find that sizeof(HoldsAnInt) == sizeof(int). This is known as the empty base optimization (EBO), and it's implemented by all the compilers I tested. If you're a library developer whose clients care about space, the EBO is worth knowing about. Also worth knowing is that the EBO is generally viable only under single inheritance. The rules governing C++ object layout generally mean that the EBO can't be applied to derived classes that have more than one base.

20、In practice, "empty" classes aren't truly empty. Though they never have non-static data members, they often contain typedefs, enums, static data members, or non-virtual functions. The STL has many technically empty classes that contain useful members (usually typedefs), including the base classes unary_function and binary_function, from which classes for user-defined function objects typically inherit. Thanks to widespread implementation of the EBO, such inheritance rarely increases the size of the inheriting classes.

21、Most classes aren't empty, so the EBO is rarely a legitimate justification for private inheritance. Furthermore, most inheritance corresponds to is-a, and that's a job for public inheritance, not private. Both composition and private inheritance mean is-implemented-in-terms-of, but composition is easier to understand, so you should use it whenever you can.

22、Private inheritance is most likely to be a legitimate design strategy when you're dealing with two classes not related by is-a where one either needs access to the protected members of another or needs to redefine one or more of its virtual functions. Even in that case, we've seen that a mixture of public inheritance and containment can often yield the behavior you want, albeit with greater design complexity. Using private inheritance judiciously means employing it when, having considered all the alternatives, it's the best way to express the relationship between two classes in your software.

23、Things to Remember

  • Private inheritance means is-implemented-in-terms of. It's usually inferior to composition, but it makes sense when a derived class needs access to protected base class members or needs to redefine inherited virtual functions.

  • Unlike composition, private inheritance can enable the empty base optimization. This can be important for library developers who strive to minimize object sizes.

<Item 40> Use multiple inheritance judiciously

24、before seeing whether a function is accessible, C++ first identifies the function that's the best match for the call. It checks accessibility only after finding the best-match function. In this case, both checkOuts are equally good matches, so there's no best match. The accessibility of ElectronicGadget::checkOut is therefore never examined.

25、To resolve the ambiguity, you must specify which base class's function to call:

mp.BorrowableItem::checkOut();              // ah, that checkOut...

 

26、Multiple inheritance just means inheriting from more than one base class, but it is not uncommon for MI to be found in hierarchies that have higher-level base classes, too. That can lead to what is sometimes known as the "deadly MI diamond"

27、C++ takes no position on this debate. It happily supports both options, though its default is to perform the replication. If that's not what you want, you must make the class with the data (i.e., File) a virtual base class. To do that, you have all classes that immediately inherit from it use virtual inheritance:

class File { ... };
class InputFile: virtual public File { ... };
class OutputFile: virtual public File { ... };
class IOFile: public InputFile,
              public OutputFile
{ ... };

  The standard C++ library contains an MI hierarchy just like this one, except the classes are class templates, and the names are basic_ios, basic_istream, basic_ostream, and basic_iostream instead of File, InputFile, OutputFile, and IOFile.

28、From the viewpoint of correct behavior, public inheritance should always be virtual. If that were the only point of view, the rule would be simple: anytime you use public inheritance, use virtual public inheritance. Alas, correctness is not the only perspective. Avoiding the replication of inherited fields requires some behind-the-scenes legerdemain on the part of compilers, and the result is that objects created from classes using virtual inheritance are generally larger than they would be without virtual inheritance. Access to data members in virtual base classes is also slower than to those in non-virtual base classes. The details vary from compiler to compiler, but the basic thrust is clear: virtual inheritance costs.

It costs in other ways, too. The rules governing the initialization of virtual base classes are more complicated and less intuitive than are those for non-virtual bases. The responsibility for initializing a virtual base is borne by the most derived class in the hierarchy. Implications of this rule include (1) classes derived from virtual bases that require initialization must be aware of their virtual bases, no matter how far distant the bases are, and (2) when a new derived class is added to the hierarchy, it must assume initialization responsibilities for its virtual bases (both direct and indirect).

My advice on virtual base classes (i.e., on virtual inheritance) is simple. First, don't use virtual bases unless you need to. By default, use non-virtual inheritance. Second, if you must use virtual base classes, try to avoid putting data in them. That way you won't have to worry about oddities in the initialization (and, as it turns out, assignment) rules for such classes. It's worth noting that Interfaces in Java and .NET, which are in many ways comparable to virtual base classes in C++, are not allowed to contain any data.

虚基类最好设计成纯虚的接口类

29、This leads to one reasonable application of multiple inheritance: combine public inheritance of an interface with private inheritance of an implementation

30、Things to Remember

  • Multiple inheritance is more complex than single inheritance. It can lead to new ambiguity issues and to the need for virtual inheritance.

  • Virtual inheritance imposes costs in size, speed, and complexity of initialization and assignment. It's most practical when virtual base classes have no data.

  • Multiple inheritance does have legitimate uses. One scenario involves combining public inheritance from an Interface class with private inheritance from a class that helps with implementation.

 

转载于:https://www.cnblogs.com/lshs/p/4593989.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
<properties> <hudson.security.AuthorizationMatrixProperty> <inheritanceStrategy class="org.jenkinsci.plugins.matrixauth.inheritance.InheritParentStrategy"/> <permission>USER:hudson.model.Item.Read:anonymous</permission> </hudson.security.AuthorizationMatrixProperty> <jenkins.model.BuildDiscarderProperty> <strategy class="hudson.tasks.LogRotator"> <daysToKeep>90</daysToKeep> <numToKeep>-1</numToKeep> <artifactDaysToKeep>30</artifactDaysToKeep> <artifactNumToKeep>-1</artifactNumToKeep> </strategy> </jenkins.model.BuildDiscarderProperty> <com.dabsquared.gitlabjenkins.connection.GitLabConnectionProperty plugin="gitlab-plugin@1.7.7"> <gitLabConnection>src.ift.run</gitLabConnection> <jobCredentialId/> <useAlternativeCredential>false</useAlternativeCredential> </com.dabsquared.gitlabjenkins.connection.GitLabConnectionProperty> <com.sonyericsson.rebuild.RebuildSettings plugin="rebuild@1.34"> <autoRebuild>false</autoRebuild> <rebuildDisabled>false</rebuildDisabled> </com.sonyericsson.rebuild.RebuildSettings> <hudson.plugins.throttleconcurrents.ThrottleJobProperty plugin="throttle-concurrents@2.11"> <maxConcurrentPerNode>3</maxConcurrentPerNode> <maxConcurrentTotal>0</maxConcurrentTotal> <categories class="java.util.concurrent.CopyOnWriteArrayList"/> <throttleEnabled>true</throttleEnabled> <throttleOption>project</throttleOption> <limitOneJobWithMatchingParams>false</limitOneJobWithMatchingParams> <paramsToUseForLimit/> <configVersion>1</configVersion> </hudson.plugins.throttleconcurrents.ThrottleJobProperty> <jenkins.branch.RateLimitBranchProperty_-JobPropertyImpl plugin="branch-api@2.1071.v1a_188a_562481"> <durationName>hour</durationName> <count>40</count> <userBoost>true</userBoost> </jenkins.branch.RateLimitBranchProperty_-JobPropertyImpl> </properties> 这是jenkin中 job定义中的一段,请解释这一段的含义
07-14
这段代码是 Jenkins Job 配置文件中的一部分,定义了一些与该 Job 相关的属性和插件配置。 让我们逐个解释每个属性的含义: 1. `<hudson.security.AuthorizationMatrixProperty>`:定义了 Jenkins 的权限矩阵属性,指定了该 Job 的权限设置。在这里,`<permission>` 元素指定了匿名用户对该 Job 的读取权限。 2. `<jenkins.model.BuildDiscarderProperty>`:定义了构建丢弃策略属性,该属性决定了构建历史的保留和丢弃规则。在这里,使用了 `<strategy>` 元素指定了构建日志保留的天数和数量限制。 3. `<com.dabsquared.gitlabjenkins.connection.GitLabConnectionProperty>`:定义了 GitLab 插件的连接属性,用于与 GitLab 服务器进行集成。在这里,`<gitLabConnection>` 元素指定了 GitLab 服务器的地址。 4. `<com.sonyericsson.rebuild.RebuildSettings>`:定义了重新构建插件的设置属性,用于允许或禁止重新构建 Job。在这里,`<autoRebuild>` 元素指定了是否自动重新构建。 5. `<hudson.plugins.throttleconcurrents.ThrottleJobProperty>`:定义了并发限制属性,用于限制并发执行 Job 的数量。在这里,`<maxConcurrentPerNode>` 元素指定了每个节点上允许的最大并发数量。 6. `<jenkins.branch.RateLimitBranchProperty_-JobPropertyImpl>`:定义了分支限制属性,用于限制 Job 分支的创建频率。在这里,`<durationName>` 元素指定了限制时间间隔的单位,`<count>` 元素指定了该时间间隔内允许的分支创建数量。 这些属性和插件配置可以根据你的需求进行修改和调整,以满足特定的工作流程和业务要求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值