...
Code Block |
---|
|
void f(const Employee &E) {
std::cout << E;
}
int main() {
Employee Coder("Joe Smith");
Employee Typist("Bill Jones");
Manager Designer("Jane Doe", Typist);
f(Coder);
f(Typist);
f(Designer);
} |
Compliant Solution (Noncopyable)
Both of the previous compliant solutions depend on consumers of the Employee
and Manager
types to be declared in a compliant manner with the expected usage of the class hierarchy. This compliant solution ensures that consumers are unable to accidentally slice objects by removing the ability to copy-initialize. If copy-initialization is attempted as in the original definition of f()
, the program is ill-formed and a diagnostic will be emitted. However, such a solution also requires the Manager
object to not attempt to copy-initialize its Employee
object, which subtly changes the semantics of the class hierarchy.
Page properties |
---|
|
I'm not entirely happy with this CS because it now has the chance to use a dangling reference to an Employee object within Manager . The original NCCE, and the previous two CSes both have a potential slicing issue with the Manager constructor as well, and that goes unaddressed. However, a case could be made that the slicing there is intentional if the author of the code does not wish to allow managers to manage other managers for some reason. It's a bit contrived, but the situation does arise in real world code where slicing is desirable, but those situations never involve virtual functions on the base class as they do here. All told, I think the examples need a bit more love. |
Code Block |
---|
|
#include <iostream>
#include <string>
class Noncopyable {
Noncopyable(const Noncopyable &) = delete;
void operator=(const Noncopyable &) = delete;
protected:
Noncopyable() = default;
};
class Employee : Noncopyable {
std::string Name;
protected:
virtual void print(std::ostream &OS) const {
OS << "Employee: " << getName() << std::endl;
}
public:
Employee(const std::string &Name) : Name(Name) {}
const std::string &getName() const { return Name; }
friend std::ostream &operator<<(std::ostream &OS, const Employee &E) {
E.print(OS);
return OS;
}
};
class Manager : public Employee {
const Employee &Assistant; // Note: this definition has been modified
protected:
void print(std::ostream &OS) const override {
OS << "Manager: " << getName() << std::endl;
OS << "Assistant: " << std::endl << "\t" << getAssistant() << std::endl;
}
public:
Manager(const std::string &Name, const Employee &Assistant) : Employee(Name), Assistant(Assistant) {}
const Employee &getAssistant() const { return Assistant; }
};
// If f() were declared as accepting an Employee, the program would be
// ill-formed because Employee cannot be copy-initialized.
void f(const Employee &E) {
std::cout << E;
}
int main() {
Employee Coder("Joe Smith");
Employee Typist("Bill Jones");
Manager Designer("Jane Doe", Typist);
f(Coder);
f(Typist);
f(Designer);
} |
Noncompliant Code Example
...