Implementations, Notes(5), Effective C++
Implementations
(I read a Chinese version of the book, any translation problem plz point out.
Postpone variable definitions as long as possible until initial values can be provided
Minimize casting
- old-style casts
- C style cast:
(T)expression
- function style cast:
T(expression)
- C style cast:
- CPP style casts
- const_cast: cast away the constness.
- dynamic_cast: safe downcasting, to decide which type a object belongs to in the inheritance hierarchy.
- reinterpret_cast: low-level cast.
- static_cast: force implicit conversions.
tips:
- dynamic_casts maybe implement based on the comparision on string names of classes.
- try to hide the cast in a function, then provide to customers, once it is a must.
- do use the CPP style cast.
Avoid returning “handles” to object internals
handles: references, pointers, iterators
try to use a copy
class GUIObject { ... };
const Rectangle boundingBox(const GUIObject& obj);
GUIobject* pgo;
// The temporary Point is destructed when the statement is over,
// and the pUpperLeft is dangling.
const Point* pUpperLeft = &(boundingBox(*pgo).upperLeft());
Strive for exception-safe code
when exception throws, exception-safe code will guarantee:
class PrettyMenu {
public:
void changeBackground(std::istream& imgSrc);
private:
Mutex mutex;
Image* bgImage;
int imageChanges;
};
void PrettyMenu::changeBackground(std::istream& imgSrc) {
lock(&mutex);
delete bgImage;
++imageChanges;
bgImage = newImage(imgSrc);
unlock(&mutex);
}
- no resources leak.
ifnew Image(imgSrc)
throws,mutex
will never release. - no data break.
ifnew Image(imgSrc)
throw,bgImg
will point to a deleted object.
and the behavior ofimageChanges
will be weird, which it changed or not.
exception-safe functions provide 3 guarantees:
- basic promise.
when exception throws, everything is in a valid state, but the users can’t predict it. - strong guarantee.
when exception throws, the program remains unchanged. - nothrow guarantee
int doSomething() throw();
though the function throws nothing, it is not guaranteed. aka., if throws, it will be a fatal error,unexpected
will be called (Googleset_unexpected
for detail).
all the properties of the functions is decided by implementation, not the declaration.
copy and swap
// pimpl idiom
struct PMImpl {
std::shared_ptr<Image> bgImage;
int imageChanges;
};
class PrettyMenu {
private:
Mutex mutex;
std::shared_ptr<PMImpl> pImpl;
}
void PrettyMenu::changeBackground(std::istream& imgSrc) {
using std::swap;
Lock m1(&mutex); // Use resource-managing class to manipulate mutex
std::shared_ptr<PMImpl> pNew(new PMImpl(*pImpl)); // Copy
pNew->bgImage.reset(new Image(imgSrc)); // Update the copy
swap(pImpl, pNew); // Swap
}
- even if strong guarantee can be implemented by
copy and swap
, but it is not implementable to all the functions. and watch the cost of copy. - exception-safety is decided by the weakest one of all the functions.
- once you have no choice but to set no any guarantee (when you call
the old code
), document it.
Understand the ins and outs of inlining
inline
just applys for inling to compiler, not forcing to do so.inline
the minimal, frequently called functions.inline
functions usually put in headers. because inlining happens in compiling period, compiler needs to know what they look like.- the functions calls virtual functions will be never inlined, because virtual functions can only be known in run-time.
- think over before inling construtors and destrutors. maybe there are complicated codes generated by compilers.
Minimize compilation dependencies between files
pimpl idiom -> handle classes
// Person.h
#include <string>
#include "datefwd.h"
#include "addressfwd.h"
class Person {
public:
Person(const std::string& name, const Date& birthday, const Address& addr);
std::string name() const;
std::string birthDate() const;
std::string address() const;
private:
std::shared_ptr<PersonImpl> pImpl;
};
// Person.cpp
#include "Person.h"
#include "PersonImpl.h"
Person::Person(const std::string& name, const Date& birthday, const Address& addr)
: pImpl(new PersonImpl(name, birthday, addr)) {}
std::string Person::name() const {
return pImpl->name();
}
abstract base class -> interface class
class Person {
public:
virtual ~Person();
virtual std::string name() const = 0;
virtual std::string birthDate() const = 0;
virtual std::string address() const = 0;
static std::shared_ptr<Person> create(const std::string& name,
const Date& birthday, const Address& addr);
};
class RealPerson: public Person {
public:
RealPerson(const std::string& name, const Date& birthday, const Address& addr)
: name(name), birthday(birthday), addr(addr) {}
virtual ~RealPerson() {}
std::string name() const;
std::string birthDate() const;
std::string address() const;
};
std::shared_ptr<Person> create(const std::string& name,
const Date& birthday, const Address& addr) {
return std::shared_ptr<Person>(new RealPerson(name, birthday, addr));
}
- don’t use objects, if object references or object pointers achieves.
- replace class definitions with class declarations as much as possible.
- provide declaration and definition with different headers.
use handle classes and interface classes to minimize changes for users during the develpment of program.
but replace with concrete classes, when the great difference in speed or size compared to coupling between classes.