1 Design Guidelines
1.1 Hightest Level
- Separate conderns (SRP)
- Design for Readability
- Design for Change and Extension (OCP, DRY)
- Design for Testability
Design pattern principles
- SRP(Single-Responsibility Principle)
- OCP(Open-Closed Principle: Open for extension and closed for modification)
- DRY(Don’t Repeat Yourself)
- LSP(Liskov Substitution Principle)
- DIP(Dependence Inversion Principle): abstraction should not rely on details, detailes should rely on abstraction
1.2 Concrete Level
- Be careful when using inheritance
- Resist the urge to put everything into one class. Seperate concerns
Inheritance is rarely the answer. Delegate to Services: Has-A Trumps Is-A” ——(Andrew Hunt, David Thomas, The Progmatic Programmer)
2 Implementation Guidelines
2.1 basic guidelines
- By default, declare single-argument constructors explicit
- Consider the alignment of data members when adding member data to a struct or class.
- Define and initialize memver variables in the order of member declaration.
- By default, make member functions const (Remember: Const correctness is part of the semantics of your class)
- Don’t make data members const or references (Remember: A class with const or reference data member cannot be copy/move assigned by default)
Eample: suppose you have the following class, it declares no copy operations, no move operations and no destructor, compiler will automatically generate these functionos if they are usedBut sometime later, it’s decided that logging the default ctor and dtorof such objects would be useful1
2
3
4
5
6
7class StringTable {
public:
StringTable() {}
... // functions for insertion, erasure, loookup, etc. but no copy/move/dtor functionality
private:
std::map<int, std::string> values;
}The above implementation would make code “moving” StringTable object actually copies them, which would introduce significant performance problem. But if we use1
2
3
4
5
6
7
8
9
10
11class StringTable {
public:
StringTable() {
makeLogEntry("Creating StringTable object");
}
~StringTable() {
makeLogEntry("Deleting StringTable object");
}
private:
std::map<int, std::string> values;
}= default
, this problem can be avoided.
2.2 special member function guidelines
- The two copy operations are independent: declaring one does not prevent compilers from generating the other
- The two move operations are not independent. If you declare either,, that prevents compilers from generating the other
- If compiler s are willing to generate the desired special function and the generated one would behave as you want, you may choose to adopt a policy of declaring it yourself and using
= default
for their definiton. [good habit, make code more readable and avoid potential side effect]
2.2.1 Overall guidelines
The Rule of 0
- If you can avoid defining default operations, do. Classes that don’t require an explicit destructor, copy constructor and copy assignment are much easier to handle.
- Try to reduce the use of pointers, since for a class with raw pointer, more consideration should be paied for destructor / copy ctor / copy assignment / move ctor and move assignment.
- If raw pointer is replaced with unique_ptr, we do not have to consider move ctor and move assignemnt, but we still have to pay attention to copy ctor and copy assignment
The Rule of 3
- When you require a destructor, you most probably also require a copy constructor and copy assignment operator [e.g. class with unique_ptr instead of raw ptr]
- If you want to copy instead of move, follow the rule of 3.
The Rule of 5
- When you require a destructor, you most probably also require the two copy operations and the two move operations [e.g. class with raw ptr]
- Since C++11, the rule of 5 may be more practical than the rule of 3, but in some situations, the rule of 3 still works
2.2.2 constructor
Condition for compiler to generate a default constructor
Default constructor’s initialization behavior
2.2.3 destructor
2.2.4 copy ctor and copy assignment
Conditions for compiler to generate default copy operations
- as long as programmer did not declare these two copy operations explicitly, compiler would always generate the copy operations, just like default constructor and destructor. But there are more constraints for move operations.
- If no copy operation is explicitly declared (first condition satisfied), but second condition or third condition failed, compiler still generate default copy operations (as long as first condition is true). However, they are (implicity) deleted 【ps: = delete means explicitly deted】
- Declaring a move operation(construction or assignment) in a class causes compilers to disable the copy operations(both), the rationale is that if member-wise move is not the proper way to move an object, there’s no reason to expect that member-wise copy is the proper way to copy it
- semantics of = delete: They are defined (will appear in the overload sets for function calls), but just disabled.
2.2.5 move ctor and move assignment
Conditions for compiler to generate default move operations
- If any of the 3 conditions failed, compiler will not generate the move operations
- interpretation of the third condition
- If all bases/data members can be move constructed/ assigned, compiler knows how to generate default move operations
- If some bases/data members cannot be move constructed/ assigned, but it can be copy constructed/ assigned, compiler also knows how to generate move operations (can be binded to corresponding copy operations) [more intepretation: Types that aren’t “move-enabled”(i.e., that offer no special support for move operations) will be “moved” via their copy operations]
- If some bases/data members can neither be move constructed/ assigned, nor be copy constructed/ assigned, compiler does not know how to generate the default operations.
3 Reference
- CPP CON Back to basics: The Special Member Functions
- CPP CON Back to basics: Designing Classes
- Effective modern C++ Item 17
Comments