[笔记]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

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;
}

posted on 2017-03-01 11:26  小硕鼠  阅读(209)  评论(0)    收藏  举报

导航