[笔记]C++笔记(3)
CH10 Composition, aggregation and association
| Property\Type | Composition | Aggregation | Association | Dependency |
|---|---|---|---|---|
| Relationship type | Whole/part | Whole/part | Otherwise unrelated | Otherwise unrelated |
| Members can belong to multiple classes | No | Yes | Yes | Yes |
| Members existence managed by class | Yes | No | No | Yes |
| Directionality | Uni-directional | Uni-directional | Uni-directional or bi-directional | Uni-directional or bi-directional |
| Relationship verb | Part-of | Has-a | Uses-a | Depends-on |
An example of aggregation (note, destroying aggregated member only deleting the pointer, the main object still exists. Distinguish this case with shallo/deep copying):
#include <string>
class Teacher
{
private:
std::string m_name;
public:
Teacher(std::string name)
: m_name(name)
{
}
std::string getName() { return m_name; }
};
class Department
{
private:
Teacher *m_teacher; // This dept holds only one teacher for simplicity, but it could hold many teachers
public:
Department(Teacher *teacher = nullptr)
: m_teacher(teacher)
{
}
};
int main()
{
// Create a teacher outside the scope of the Department
Teacher *teacher = new Teacher("Bob"); // create a teacher
{
// Create a department and use the constructor parameter to pass
// the teacher to it.
Department dept(teacher);
} // dept goes out of scope here and is destroyed, but it only destroyed *pointer* to m_teacher (only deleted *m_teacher)
// Teacher still exists here because dept did not delete m_teacher
std::cout << teacher->getName() << " still exists!";
delete teacher;
return 0;
}
CH11 Inheritance
C++ constructs derived classes in phases, starting with the most-base class (at the top of the inheritance tree) and finishing with the most-child class (at the bottom of the inheritance tree). So the parent class knows nothing about its child class.
To specify specific base constructor when instantiating derived class, do:
class Derived: public Base
{
public:
double m_cost;
Derived(double cost=0.0, int id=0)
: Base(id), // Call Base(int) constructor with value id!
m_cost(cost)
{
}
double getCost() const { return m_cost; }
};
The protected access specifier allows the class, the member belongs to, friends, and derived classes to access the member.
Recall: default access of class variables are private, default inheritance of a class is also private.
Access control:
| Access specifier in base class | Access specifier when inherited publicly | Access specifier when inherited privately | Access specifier when inherited protectedly |
|---|---|---|---|
| Public | Public | Private | Protected |
| Private | Inaccessible | Inaccessible | Inaccessible |
| Protected | Protected | Private | Protected |
Use public inheritance unless you have a specific reason to do otherwise.
Use static_cast to call friend function of base class.
class Base
{
private:
int m_value;
public:
Base(int value)
: m_value(value)
{
}
friend std::ostream& operator<< (std::ostream &out, const Base &b)
{
out << "In Base\n";
out << b.m_value << '\n';
return out;
}
};
class Derived : public Base
{
public:
Derived(int value)
: Base(value)
{
}
friend std::ostream& operator<< (std::ostream &out, const Derived &d)
{
out << "In Derived\n";
// static_cast Derived to a Base object, so we call the right version of operator<<
out << static_cast<Base>(d);
return out;
}
};
int main()
{
Derived derived(7);
std::cout << derived;
return 0;
}
using -declarations are the preferred way of changing access levels.
Make a public member in base class private in derived class:
#include <iostream>
class Base
{
public:
int m_value;
};
class Derived : public Base
{
private:
using Base::m_value;
public:
Derived(int value)
// We can't initialize m_value, since it's a Base member (Base must initialize it)
{
// But we can assign it a value
m_value = value;
}
};
int main()
{
Derived derived(7);
// The following won't work because m_value has been redefined as private
std::cout << derived.m_value;
return 0;
}
Multiple inheritance potentially leads to "diamond inheritance". a->b,c->d.
Avoid multiple inheritance unless alternatives lead to more complexity.
CH12 Polymorphism, virtual functions and casting
A virtual function is a special type of function that, when called, resolves to the most-derived version of the function that exists between the base and derived class. This capability is known as polymorphism.
Only the most base class function needs to be tagged as virtual for all of the derived functions to work virtually. However, it’s generally a good idea to use the virtual keyword for virtualized functions in derived classes even though it’s not strictly necessary.
A trick: because derived class is constructed/destructed after/before its base class, so do not call virtual functions in constructor/destructor of derived class.
override and final are not keywords of C++, they are normal identifiers that have special meaning in certain contexts.
Apply the override specifier to every intended override function you write. The compiler would have complained if the intended function is not overrode. This will not take you extra performance cost.
Covariant return type is considered to be a legal override:
class Base
{
public:
// This version of getThis() returns a pointer to a Base class
virtual Base* getThis() { return this; }
};
class Derived: public Base
{
// Normally override functions have to return objects of the same type as the base function
// However, because Derived is derived from Base, it's okay to return Derived* instead of Base*
virtual Derived* getThis() { return this; }
};
Whenever you are dealing with inheritance, you should make any explicit destructors virtual. (because if you use base pointer to refer derived class, only base class destructor is called.)
Function binding and virtual table:
Static binding, which binds specific address to function. Dynamic binding, which uses function pointers.
Example:
#include <iostream>
int add(int x, int y)
{
return x + y;
}
int main()
{
// Create a function pointer and make it point to the Add function ->Dynamic binding
int (*pFcn)(int, int) = add;
// Static binding
int c = add(1, 2);
std::cout << pFcn(5, 3) << std::endl; // add 5 + 3
return 0;
}
The virtual table is a lookup table of functions used to resolve function calls in a dynamic/late binding manner. It is a static array that stores eligible functions a class can call constructed during compilation. Henceforth, it has slight performance impact:
- Direct function call: resolve physical address
- Function pointer: resolve function, resolve physical address
- Virtual function: look up vtable, resolve function, resolve physical address
Pure virtual functions, abstract base classes, and interface classes
Pure virtual function (meant to be redefined in derived classes) virtual returntype funcname() = 0; (you can also put a body in this function and let derived class use base function) The according class becomes abstract and cannot be instantiated. Any derived class must define a body for derived pure virtual function, or that derived class will be considered an abstract base class as well.
Include a (usually default, i.e., empty) virtual destructor for interface classes, so that the proper derived destructor will be called if a pointer to the interface is deleted.
Virtual base classes (virtual inheritance)
class derived : virtual public/private/protected base. Derived classes share same member from base class.(diamond inheritance)
Example:
#include <iostream>
class PoweredDevice
{
public:
PoweredDevice(int power)
{
std::cout << "PoweredDevice: " << power << '\n';
}
};
class Scanner: virtual public PoweredDevice // note: PoweredDevice is now a virtual base class
{
public:
Scanner(int scanner, int power)
: PoweredDevice(power) // this line is required to create Scanner objects, but ignored in this case (copier takes charge of this)
{
std::cout << "Scanner: " << scanner << '\n';
}
};
class Printer: virtual public PoweredDevice // note: PoweredDevice is now a virtual base class
{
public:
Printer(int printer, int power)
: PoweredDevice(power) // this line is required to create Printer objects, but ignored in this case (copier takes charge of this)
{
std::cout << "Printer: " << printer << '\n';
}
};
class Copier: public Scanner, public Printer
{
public:
Copier(int scanner, int printer, int power)
: Scanner(scanner, power), Printer(printer, power),
PoweredDevice(power) // PoweredDevice is constructed here
{
}
};
a virtual base class is always considered a direct base of its most derived class (which is why the most derived class is responsible for its construction). But classes inheriting the virtual base still need access to it. So in order to facilitate this, the compiler creates a virtual table for each class directly inheriting the virtual class (Printer and Scanner). These virtual tables point to the functions in the most derived class. Because the derived classes have a virtual table, that also means they are now larger by a pointer (to the virtual table)
Object slicing and dynamic casting
Derived class is "sliced" to be base type while assigning (=) to base class (because = is not virtual). So it is always a good idea to pass by reference.
Example:
void printName(const Base &base) // note: base passed by reference
{
std::cout << "I am a " << base.getName() << '\n';
}
void printName2(const Base base) // note: base now passed by value, will be sliced and derived virtual functions won't be evaluated.
{
std::cout << "I am a " << base.getName() << '\n';
}
int main()
{
Derived d(5);
printName(d);
return 0;
}
Object slicing in std::vector:
#include <vector>
#include <functional> // for std::reference_wrapper
int main()
{
std::vector<std::reference_wrapper<Base> > v; // our vector is a vector of std::reference_wrapper wrapped Base (not Base&)
Base b(5); // b and d can't be anonymous objects
Derived d(6);
v.push_back(b); // add a Base object to our vector
v.push_back(d); // add a Derived object to our vector
// Print out all of the elements in our vector
for (int count = 0; count < v.size(); ++count)
std::cout << "I am a " << v[count].get().getName() << " with value " << v[count].get().getValue() << "\n"; // we use .get() to get our element from the wrapper
return 0;
}
Use dynamic_cast to do downcasting (cast base to derived). So sometimes you don't need virtual functions.
Failure of dynamic_casting a pointer leads null pointer. Always ensure your dynamic casts actually succeeded by checking for a null pointer result. (You can also dynamic_cast a reference, it will throw std::bad_cast when it fails)
You can also use static_cast (is faster than dynamic_cast) to do downcasting, but it will always "successful", i.e., doesn't return null pointer if it fails.
Derived *d = dynamic_cast<Derived*>(b); // use dynamic cast to convert Base pointer into Derived pointer
if (d) // make sure d is non-null
std::cout << "The name of the Derived is: " << d->getName() << '\n';
In general, using a virtual function should be preferred over downcasting, except:
- When you can not modify the base class to add a virtual function
- When you need access to something that is derived-class specific
- When adding a virtual function to your base class doesn’t make sense (e.g. there is no appropriate value for the base class to return)
An example to "virtualize" <<:
#include <iostream>
class Base
{
public:
Base() {}
// Here's our overloaded operator<<
friend std::ostream& operator<<(std::ostream &out, const Base &b)
{
// Delegate printing responsibility for printing to member function print()
return b.print(out);
}
// We'll rely on member function print() to do the actual printing
// Because print is a normal member function, it can be virtualized
virtual std::ostream& print(std::ostream& out) const
{
out << "Base";
return out;
}
};
class Derived : public Base
{
public:
Derived() : Base() {}
// Here's our override print function to handle the Derived case
virtual std::ostream& print(std::ostream& out) const override
{
out << "Derived";
return out;
}
};
int main()
{
Base b;
std::cout << b << '\n';
Derived d;
std::cout << d << '\n'; // note that this works even with no operator<< that explicitly handles Derived objects
Base &bref = d;
std::cout << bref << '\n';
return 0;
}
浙公网安备 33010602011771号