Effective C++ 55 Things To Improve Your Program

Author of the book is Scott Meyer

Item 1 C++ is a combination of 4 aspect

C

C++ is a super type of C and contains primitive structure and low level constructs that are inherently unsafe. When working with such code there doesn’t exist any templates, exceptions and overloading.

Object Oriented C++

C++ was built on the foundation of class ideas such as [[encapsulation]], [[inheritance]], [[polymorphism]], [[virtual functions]].

Template C++

Templates provide good way to generalize code using the idea similar to generics in java. In C++, this can be used within classes, functions and perform type conversion during runtime to simplify code.

STL

C++ provides standard library with set of container, iterator and algorithms.

Key Take Away

  • Effective C++ Programming depends on the parts of C++ you are using

Const, enums over #defines

Constants

Use

class Widget {
	private:
		static const float PI = 3.14; // simple example 
		static const double RATIO; // out of line example
		enum { SIZE = 5 }; // this could be an option too
		int gears[SIZE]; 
}

// implementation of ratio
Widget::RATIO = 1.5;

Instead of

#define PI 3.14
#define RATIO 1.5
#define SIZE 5

Key Insights

  • #define often pollute the entire compilation and will continue to do so unless #undefined
  • Some time you might need to implement the constant out of line.
  • enum variable reference can’t be accessed

Functions

Use

template<typename T> // because we don't know what T is, we
inline void callWithMax(const T& a, const T& b){              // pass by reference-to-const
  f(a > b ? a : b);
}

Instead of

#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))

CALL_WITH_MAX(++a, b);          // a is incremented twice
CALL_WITH_MAX(++a, b+10);       // a is incremented once

Key Insight

  • Inline function doesn’t pollute the name space unless imported
  • Defined macro can lead to inconsistent input if input is complex

Key Take Away

  • Use const in object instead of #define constant
  • For function like macro, prefer inline function to #define

Use Const when possible

Const Pointer Vs Const Data

char greeting[] = "hello";
char *p = greeting;                     // non-const pointer,
                                        // non-const data
const char *p = greeting;               // non-const pointer,
                                        // const data
char * const p = greeting;              // const pointer,
                                        // non-const data
const char * const p = greeting;        // const pointer,
										// const data

Iterator STL Pattern

std::vector<T>::iterator in STL uses the type T* const which means that the content of the iterator can be modified. For content immutable iterator, use the std::vector<T>::const_iterator . For example

const std::vector<int>::iterator iter = vec.begin();
// iter acts like a T* const
*iter = 10; // OK, changes what iter points to
++iter; // error! iter is const

std::vector<int>::const_iterator cIter = vec.begin();  
//cIter acts like a const T*
*cIter = 10; // error! *cIter is const
++cIter; // fine, changes cIter

Apply const to function return type to avoid potential comparison error

Say if we have the following

const Rational operator*(const Rational& lhs, const Rational& rhs);

the additional const would prevent the following code from compiling successfully

if ((a*b) = c) { // typo should have been ==
...
}

Over load Const Member Function

const member function is where you add const after the function argument specifying that the function would note mutate any non static field or parameter within the function code.

class TextBlock {
	const char& operator[](std::size_t position) const { 
		return text[position]; 
	} // operator[] for const objects

	char& operator[](std::size_t position){ 
		return text[position]; 
	} // operator[] fornon-const objects
}

usage might look something like this

TextBlock tb("Hello");
std::cout << tb[0];// calls non-const TextBlock::operator[]
tb[0] = 'x'; // fine 
const TextBlock ctb("World");
std::cout << ctb[0]; // calls const TextBlock::operator[]
ctb[0] = 'x'; // error!

Mutable Flags

When you would like some field to be still mutable even in certain situation you marked the function as const member, you can use the mutable flag. Here is an example.

class CTextBlock {
public:
std::size_t CTextBlock::length() const{
	if (!cacheValid_){
		length_ = computeLength(); // fine
	}
	return length_;
}
private: 
	mutable int length_;
	bool cacheValid_;
}

Avoid Code Duplication With Const and non Const member function

Sometimes we might have code which are very same across the const and non-const member function which we but we would like to avoid duplication by maintaining two implementation. We can do the following.

class TextBlock {
public:
	// const version
	const char& operator[](std::size_t position) const { 
		...
		// step 1, 2, 3 and so on
	    return text[position];
	}
	
	// non const version
	char& operator[](std::size_t position){
    return
	    // cast away const on op[]'s return type;
		const_cast<char&>(
			// add const to *this's type;
			// call const version of op[]
			static_cast<const TextBlock&>(*this)[position]
		);
	}
};

With this implementation, we know only have one implementation and avoided duplicate code across both side.

Note that the other way will not work because having const version calling non const version could potentially violate the const-ness of as non const version doesn’t have the constraint.

Key Take Away

  • Declaring Const help compiler detect error. const can be apply to primitives, objects, function parameter, member function, and return type
  • Compiler enforce bitwise correctness to prevent mutation in function, but passing reference out side technically doesn’t violate this constraint but this allows the caller to still mutate the output.
  • When const and non-const has same implementation, use the non-const to call the const version.

Initialize Object

Class Initializer

In most cases, we can initialize the object by using the following syntax.

// h file
class Widget {
public:
Widget(const std::string& name);
private:
	std::string name_;
};

// cpp file
Widget::Widget(const std::string& name): name_(name) {
// other logic here
}

Using initialization instead of assignment can allow more optimization to be done by the compiler.

Explicit Initializer

When you pass in empty into the initializer. It will use the default constructor of that particular field.

class Widget {
public: 
Widget(): vec_() { // here vector is initialized to default

}
private:
	std::vector<int> vec_;
}

Non Deterministic Of Non Local Static Object Initialization

If you have a non local (i.e global static object) like the following

// file 1
class Widget {
...
};
extern Widget w;

and that another static object depends on

class Table {

public: 
	Table() {
		int size = w.size();
	}
};

Table t;

The C++ run time does not guarantee that w is available during the initialization of table t. Therefore, we could run into undefined behavior. Alternatively we can do the following.

class Widget {
...
};

Widget& getGlobalWidget(){
	static Widget w;
	return w;
}

class Table {
	Table::Table(){
		...
		int size = getGlobalWidget().size();
		...
	}
...
}
Table& getGlobalTable() {
	static Table t;
	return t;
}

This way it guarantees that global widget is always initialized as it is done in part of the execution flow.

Key Take Away

  • Manually initialize object of built in type. Because C++ only sometime initialize them.
  • Prefer the use of initialization list to assignment inside the body of the constructor
  • Avoid initialization order problem by replacing non local static object with local static object (i.e inside function).

Default class constructor, destructor, and assignment operator

Default generated functions

When you create a class without any explicit constructor, destructor, and assignment operator. C++ will generate a default inline set for you. If we have the following

class Empty {}

It is equivalent to

class Empty {
public:
Empty() { ... }
Empty(const Empty& rhs) { ... } // copy all non static data
~Empty(){ ... } 
Empty& operator=(const Empty& rhs) { ... } // copy all non static data
}

Reference field inside a class

When you have a reference field within a class

class Person {
	Person(String& name);
	private:
		String& name_;
}

In this case, when you attempt to assign one person to another, compiler will refused to do so as C++ is unable to reassign references field.

Key Insight

  • Compilers may implicitly generate a class’s default constructor, copy constructor, copy assignment operator, and destructor.

Block Generated Function

In order to prevent generated function as well as their usage we could mark the copy, assignment and other generated operator as private and don’t implement them which would result in a linker error when the function is called. Here is a simple example.

class Uncopyable {
protected:
	Uncopyable() {}
	~Uncopyable() {}
private:
	// don't implement this in cpp files
	Uncopyable(const Uncopyable&); 
	Uncopyable& operator=(const Uncopyable&);
}

Key Insight

  • 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.

Declare Destructor Virtual

Make Base Class Virtual

When using a base class with sub classes, make sure to mark the destructor as virtual as this forces the sub class to implement the destructor and properly free its resources as the base class won’t be able to see the children’s resources and can lead to memory leak. Here is an example.

class Car {
public:
	Car();
	virtual ~Car();
};

Car* c = (Car*) new Limo();
// Without the virtual destructor 
// we would only run the car distructor. 
// Often lead to memory leak
delete c; // run the Limo destructor

Non Base Class Should Not have virtual destructor

Class with virtual destructor will increase the overall size due to the additional vptr it must also carry along to indicate the virtual function it posses. For non base class that doesn’t get inherited should not use virtual constructor.

Be Careful When Inheriting Classes From Third Party

When you inherit from third party library such as std::string or any other common classes. Be mindful that they may not have virtual destructor and later casting them into their base class can easily lead to memory leak.

Key Insights

  • Polymorphic base classes should declare virtual destructors. If a class has any virtual functions, it should have a virtual destructor.
  • Classes not designed to be base classes or not designed to be used polymorphically should not declare virtual destructors.

Do not throw exception inside destructor

Exception in destructor can cause memory leak

When destructor of the object throws exception, it can cause other objects that’s within a same set of a collection (e.g vector, list, map) to escape the original control flow which may resulted in other object within the same collection to not properly allocated and create memory leak. However, it may not always be possible to avoid this. Below are three ways to

Handle the exception in destructor by terminating program

In certain situation, it may be best to terminate the program if the exception is severe and is unable to recover. In this case, the best way is to terminate the program. One way to perform this would be to catch the exception and use std::abort() to close the program. Example

DBConn::~DBConn(){
	try { 
		db.close(); 
	}
	catch (...) {
		// make log entry that the call to close failed;
		std::abort();
	}
}

Handle exception by swallowing the exception

This is generally a bad idea as it suppress the fact that something failed and can become bigger issue later on. However, in very rare situation, we can suppress the error assuming that the program can continue to execution as normally would. In most cases this is not the case.

DBConn::~DBConn(){
	try { 
		db.close(); 
	}
	catch (...) {
		// make log entry that the call to close failed;
		// continue execution here
	}
}

Pass responsibility to clients/caller

Another potential way to handle this situation is to offer the api which could cause the exception in question to be called by the caller. In our db connection example. We could create the following function to let caller handler the exception. Below is an example.

class DBConn { 
public: ... 
void close() { // new function for client to use 
	db.close(); 
	closed = true; // flag used by the destructor
} 
~DBConn() { 
	if (!closed) { // note this should be a back up call 
		try { 
			// close the connection if the client didn't
			db.close(); 
		}
	catch (...) { // if closing fails,
		// make log entry that call to close failed;   
		// note that and terminate or swallow
	}
}
private:
	DBConnection db;
	bool closed;
};

Key Insights

  • Destructor should never emit exceptions. If function used by destructor throws any exception, the destructor should catch them and decide to terminate the program or swallow them.
  • Class should provide another set of wrapper function to help handle the free up and handle any exception during execution by the caller rather than inside the destructor. Leave destructor call as backup case.

Don’t Call Virtual Function Inside Constructor or Destructor

Base class function will be called inside the constructor.

Let’s say we have a base class which contains a set of function to run in the constructor. If we have the derived (child) class override those function inside their implementation, they will still run THE ORIGINAL base class function. This is unlike other OO languages such as java or C# where function overriding occur throughout.

class Car {
	Car(){
		buildEngine();
	}
	virtual void buildEngine(){
		cout << "click"; // this one would be ran
	}
}

class SuperCar: Car {
	//...
	SuperCar(){
	
	}
	virtual void buildEngine() {
		// this would not be run 
		cout << "roff"; 
	}
}

SuperCar sc; // Click

Knowing the this fact, running virtual function in the base class constructor or destructor will result in linker error or worse (unexpected run behavior) if that function exists with implementation within the base class.

Resolve the generalization through dependency injection

One to resolve this is to pass the unique implementation detail such as necessary class information and detail of that particular shared virtual function into the base class constructor and let the base class run the code. For example,

class Car {
	Car() {
		buildEngine("click")
	}
	Car(std::string engineString){
		buildEngine(engineString);
	}
private: 
	void buildEngine(std::string engineString){
		cout << engineString;
	}
}

class SuperCar: Car {
	//...
	SuperCar(): Car("roff"){
	}
}

SuperCar sc; // Roff

Key Insight

  • Don’t call virtual function during constructor or destructor as it will not achieve what you expect and can cause confusion and hard to debug.

Return reference to this for assignment operator

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

//...
Widget a, b, c;
// some assignment
a = b = c; // assign b to c and the assigned b to a

While this is not very commonly used, it is a convention followed by the std library and should be followed to avoid any inconsistency when working with the api of std library.

Key Insight

  • By convention of other std library. Assignment are often chained and return *this will help address any inconsistency.

Check for self when using assignment operator

When class contains pointer to a dynamically allocated item (new, malloc, etc), if we do not check for identity (i.e rhs is same as self), we could run into issues where we free the dynamically allocated fields which also freed the rhs object which resulted in inconsistency/unexpected states. To combat this, we do the following

Widget& Widget::operator=(const Widget& rhs){
	if(this == &rhs) return *this;
	auto ptr = new Object(*rhs.dynamicPtr);
	delete dynamicPtr; //dynamically allocated pointer
	dynamicPtr = ptr;
	return *this;
}

Note we assign the value to a temporary variable first. The reason for this is because if initializing new object caused an exception, we would be in an inconsistency state again.

Key Insigts

  • Make sure operator= is well-behaved when an object is assigned to itself. Techniques include comparing addresses of source and target objects, careful statement ordering, and copy-and-swap.
  • Make sure that any function operating on more than one object behaves correctly if two or more of the objects are the same.

Copy Function Should Copy All the Attribute

When we define our con assignment operator (i.e operator=(...)) or copy operator (i.e Widget(const Widget& rhs)), the compiler will not raise any warning when we missed a field or copied incorrectly. This can often lead to bug where new field is updated and the constructor and copy function and not updated which lead to inconsistent states.

class Widget {
public:
	Widgt(const Widget& rhs)
		: size_(rhs.size_) { 
		// forgot to copy the direction, 
		// no warning are raised.
	}
	Widget& operator=(const Widget& rhs){
		size_ = rhs.size_;
		// forgot to copy the direction, 
		// no warning are raised.
	}
private:
	int size_;
	int direction_; // recently added field
}

Derived Class Must Copy all the base class field

When a derived class is copied, the field under the sub class is not copied. The responsibility is on the derived class. The following might work in java but will often lead to error in cpp.

class FastWidget: public Widget {
public:
	FastWidget(const FastWidget& rhs)
		: speed_(rhs.speed_) { 
		// forgot to copy the, name and direction, 
		// no warning are raised.
	}
	Widget& operator=(const Widget& rhs){
		speed_ = rhs.speed_;
		// forgot to copy the, name and direction, 
		// no warning are raised.
		return *this; // don't forget return type
	}
private:
	int speed_;
}

The correct approach here is to call the base class copy/assignment operator like so.

class FastWidget: public Widget {
public:
	FastWidget(const FastWidget& rhs)
		: Widget(rhs), speed_(rhs.speed_) { 
		// forgot to copy the, name and direction, 
		// no warning are raised.
	}
	Widget& operator=(const Widget& rhs){
		Widget::operator=(rhs);
		speed_ = rhs.speed_;
		// forgot to copy the, name and direction, 
		// no warning are raised.
		return *this; // don't forget return type
	}
private:
	int speed_;
}

Avoid duplicate code by using init

It doesn’t make sense for copy operator to call assignment operator or vice versa. However, there might be many derived logic that are similar among the two. To combat the duplication, it might be helpful to create a init function that assign the fields based on input and reuse across both operator and copy.

Key Insights

  • Copying function should be made sure to copy all of the object data and the base class parts.
  • Double check if there are custom copy/=operator for a class before adding a field
  • Avoid duplication across copy and = operator by having init function that assign the fields.

Use objects to manage resources

Delete keyword inside object destructor

It is more preferential to have the life cycle events to initialize and close the resources managed within object constructor and destructor. The reason being that during the code execution, when exception or any unexpected change in execution location (goto, break, return, etc), the object destructor will be called to ensure that memory, db, connection, sockets, or any other resources are closed and leave no open loops that could cause many bad consequences.

Use of smart pointer to handle deletion new references

A commonly used object used in cpp to manage the life cycle of a newly initialized object in the heap is std::autoptr. The usage is as follow.

{
	std::auto_ptr<Foo> pFoo(new Foo());
	// .. do something with foo
	pFoo->bar();
} // foo is automatically deallocated, i.e deleted

Note that the autoptr doesn’t support any copying which and rather handles copy and assignment operation as transfer ownership. Take the following example.

std::auto_ptr<Investment> pInv1(new Investment()); 
std::auto_ptr<Investment> pInv2(pInv1); 
// pass ref from pInv1 to pInv2. 
// pInv1 no longer have this object and point to null
pInv1 = pInv2;
// pass ref from pInv2 to pInv1. 
// pInv2 no longer have this object and point to null

Another commonly used object is std::shared_ptr which allows the copy of pointer and will only delete the pointer once all the references to that object have been out of scope.

{
	std::shared_ptr<Investment> pInv1(new Investment()); 
	{
		std::shared_ptr<Investment> pInv2(pInv1); 
		// do stuff with pInv2
	}
	{
		std::shared_ptr<Investment> pInv3(pInv1); 
		// do stuff with pInv3
	}
} // no reference to pInv1 exist anymore, deleted

Limitation of Smart Pointers

Unlike the garbage collector like java or C#, c++ shared pointer will not prevent cycles when it comes to shared pointers.

shared_ptr<ListNode> node1 = new ListNode();
shared_ptr<ListNode> node2 = new ListNode();
node1->setNext(node2);
node2->setNext(node1);
// this creates a cycle between two shared pointer and will never be reclaimed.

The smart pointer does not work with some other native array type such as the following

std::auto_ptr<std::string> aps(new std::string[10]); 
// only delete will be used
std::tr1::shared_ptr<int> spi(new int[1024]);    
// same problem

Key Take Away

  • To prevent resource leaks, use RAII objects that acquire resources in their constructors and release them in their destructors.
  • Two commonly useful RAII classes are shared_ptr and auto_ptr. shared_ptr is usually the better choice, because its behavior when copied is intuitive. Copying on auto_ptr sets it to null.

Define Copy Behavior In Resource Managing Classes

Prohibit Copying

In many cases, it makes no sense to allow RAII objects to be copied. This is likely to be true for a class like Lock, because it rarely makes sense to have “copies” of synchronization primitives. When copying makes no sense for an RAII class, you should prohibit it. Item 6 explains how to do that: declare the copying operations private. For Lock, that could look like this.

clss Lock: private Uncopyable {
public:
	// ...
} 

See [[#Block Generated Function]] for detail implementation of Uncopyable.

Reference Counting

Sometimes it’s desirable to hold on to a resource until the last object using it has been destroyed. When that’s the case, copying an RAII object should increment the count of the number of objects referring to the resource. This is the meaning of “copy” used by std::shared_ptr. One way to achieve this is through the use of the shared_ptr by using the second argument of to specify the deleter function. Here is an example.


class Lock {
public: 
	void unlock(Mutex* pm) {
		// unlock logic here
		// ...
	}
	explicit Lock(Mutex *pm): mutexPtr(pm, unlock) {
		lock(mutexPtr.get());   
		// see Item 15 for info on "get"
	}
private:
	std::shared_ptr<Mutex> mutexPtr;
}

In the above example, we see the unlock function being passed as second argument when initializing the shared ptr, the function will be called instead of the default delete keyword when the reference reaches zero.

Copy The Underlying Resources

Sometimes you can have as many copies of a resource as you like, and the only reason you need a resource-managing class is to make sure that each copy is released when you’re done with it. In that case, copying the resource-managing object should also copy the resource it wraps. That is, copying a resource-managing object performs a “deep copy.” Some implementations of the standard string type consist of pointers to heap memory, where the characters making up the string are stored. Objects of such strings contain a pointer to the heap memory. When a string object is copied, a copy is made of both the pointer and the memory it points to. Such strings exhibit deep copying.

Transfer Ownership

On rare occasion, you may wish to make sure that only one RAII object refers to a raw resource and that when the RAII object is copied, ownership of the resource is transferred from the copied object to the copying object. This is the meaning of “copy” used by auto_ptr.

Key Insights

  • Copying an RAII object entails copying the resource it manages, so the copying behavior of the resource determines the copying behavior of the RAII object.
  • Common RAII class copying behaviors are disallowing copying and performing reference counting, but other behaviors are possible.

Define Raw Access-or For Resources Manage Classes

In many situation, the external API might often work with the underlying resource class which means that the resource manage class we use to protect the resource leak should either provide underlying API inside the resource class implementation or provide the access to the raw resources when needed. While it is safer to provide the internal API, it isn’t always practical as there could be large number of dependency that are only required in certain use cases but isn’t always required. In most case, it is most probable to have raw access for the sake of simplicity.

Offer Explicit Get Function

In most situation, the prefer way is to provide a explicit getter which fetches the underlying resources reference. In the std::shared_ptr and std::auto_ptr we have the get() interface which returns the reference of the type within their template. For example,

// api
int getTotalCost(const Investment* pi);
class Investment {
// ...
}

std::shared_ptr<Investment> pInv(createInvestment());
getTotalCost(pInv.get()); // call the 

In some situation, it might also be preferable to have deference in order to call the internal function. We could use the arrow function for std::shared_ptr and * for std::auto_ptr. For example

class Investment {
public:
	bool isTaxFree() const;
}
std::shared_ptr<Investment> pInv(createInvestment());
pInv->isTaxFree();
std::auto_ptr<Investment> autoInv(createInvestment());
(*autoInv).isTaxFree();

Implicit Casting To Resource Type

Sometimes, if the underlying resources is constantly being used and the constant use of .get() API is causing developer inconvenience and causing developer to work around that by not using the manage class. In these cases, it might be possible to add a implicit conversion operator to support the functionality. Suppose we have the following.

void changeFontSize(FontHandle f, int newSize);

class Font {
public:
	operator FontHandle() const { return f; }
private:
	FontHandle f; // the underlying resources
}
Font font(getFontHandle());
int newSize = 20;
changeFontSize(font, newSize); 
// font will get cast to font handle, all is good

This makes the usage of the API easy and strait forward. However, it is still often not preferred because the implicit casting can often lead to unexpected behavior. Especially when the developer is unfamiliar with the code base and some of these more nuanced use cases. One such example would be.

FontHandle f2;
{
	Font f1(getFontHandle());
	f2 = f1; 
	// was copy to get another Font or 
	// increase reference counting
} // f1 is out of scope and destroyed
f2.doStuff() // err f1 is already destroyed.

In the above case, since the cast to raw resource was unexpected, the deletion of the object when out of scope is also unexpected. As rule of thumb, try to not have too many implicit casting for resource management classes.

Key Insights

  • APIs often require access to raw resources, so each RAII class should offer a way to get at the resource it manages.
  • Access may be via explicit conversion or implicit conversion. In general, explicit conversion is safer, but implicit conversion is more convenient for clients.

Corresponding of new and delete

C++ offers ways to initialize arrays using the new syntax. For example.

std::string * stringArr = new std::string[100];
// ...
delete stringArr;

However, simply running the delete on the pointer is not sufficient as delete will treat the variable as a single object and only delete the first instance of the array, leaving 99 more instances not properly destroyed. To properly destroy array based initialized object. We can do the following.

std::string * stringArr2 = new std::string[100];
delete [] stringArr2;

In this implementation, we properly free the entire array of object. Note that improper use of [] on singular object will cause undefined behavior as it will treat the content of the object as part of the array count and possibly free unintended memory.

std:string* str = new std::string;
delete [] str; // undefined behavior

To avoid these problematic usage, try to resort to the more standard collection such as std::vector to properly free the item and make the maintaining the code easier.

Key Insights

  • If you use [] in a new expression, you must use [] in the corresponding delete expression. If you don’t use [] in a new expression, you mustn’t use [] in the corresponding delete expression.

Declare Share Pointer And New in single statement

Key Take Away

  • Store new objects in smart pointers in standalone statements. Failure to do this can lead to subtle resource leaks when exceptions are thrown.

Item 18 - Make interface easy to use correctly but hard to use incorrectly

Example 1 - Date as input

struct Day {
explicit Day (int d):val(d){}
int val;
}
struct Month {
explicit Month (int m):val(m){}
int val;
}
struct Year {
explicit Year (int y):val(y){}
int val;
}
struct Date {
explicit Date (Year y, Month m, Day d){
...
}
}
Date d(30, 3, 1995); // wrong types
Date d2(Day(30), Month(3), Year(1995)); // wrong order
Date d3(Year(1995), Month(3), Day(30)); // correct

Key Take Away

  • Good interfaces are easy to use correctly and hard to use incorrectly. Your should strive for these characteristics in all your interfaces.
  • Ways to facilitate correct use include consistency in interfaces and behavioral compatibility with built-in types.
  • Ways to prevent errors include creating new types, restricting operations on types, constraining object values, and eliminating client resource management responsibilities.
  • TR1::shared_ptr supports custom deleters. This prevents the cross-DLL problem, can be used to automatically unlock mutexes (see Item 14), etc.

Item 19: Treat class design as type design

Questions to ask when thinking about a new class

  • What kind of type conversions are allowed for your new type?
    • Your type exists in a sea of other types, so should there be conversions between your type and other types? If you wish to allow objects of type T1 to be implicitly converted into objects of type T2, you will want to write either a type conversion function in class T1 (e.g., operator T2) or a non-explicit constructor in class T2 that can be called with a single argument. If you wish to allow explicit conversions only, you’ll want to write functions to perform the conversions, but you’ll need to avoid making them type conversion operators or non-explicit constructors that can be called with one argument. (For an example of both implicit and explicit conversion functions, see Item 15)
  • What operators and functions make sense for the new type?
    • The answer to this question determines which functions you’ll declare for your class. Some functions will be member functions, but some will not (see Items 23, Item 24 and Item 46).
  • What standard functions should be disallowed?
    • Those are the ones you’ll need to declare private (see Item 6).
  • Who should have access to the members of your new type?
    • This question helps you determine which members are public, which are protected, and which are private. It also helps you determine which classes and/or functions should be friends, as well as whether it makes sense to nest one class inside another.
  • What is the “undeclared interface” of your new type?
    • What kind of guarantees does it offer with respect to performance, exception safety (see Item 29), and resource usage (e.g., locks and dynamic memory)? The guarantees you offer in these areas will impose constraints on your class implementation.
  • How general is your new type?
    • Perhaps you’re not really defining a new type. Perhaps you’re defining a whole family of types. If so, you don’t want to define a new class, you want to define a new class template.
  • Is a new type really what you need?
    • If you’re defining a new derived class only so you can add functionality to an existing class, perhaps you’d better achieve your goals by simply defining one or more non-member functions or templates.

Key Inisght

  • Class design is type design. Before defining a new type, be sure to consider all the issues discussed in this Item.

Item 27

  • Avoid cast when possible. If needed use the 4 dedicated cast instead of the native C cast to improve on ease of code search and compiler understanding.
  • static_cast used to cast object type like (int to float).
  • const_cast cast away the const-ness of the type
  • dynamic_cast change from parent type (base) to children class (derived). Very expensive when nesting type. Each layer is one strcmp.
  • reinterpret_cast low level cast that is used for portability and platform specific purpose. Very rarely used.