Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Added a new CS, but I'm not entirely pleased with it.

...

Code Block
bgColor#ccccff
langcpp
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
hiddentrue

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
bgColor#ccccff
langcpp
#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

...