Classes¶

final¶

  • final specifier can be used on a class to disallow derived classes
class base {
public:
    virtual void member() = 0;
};

class derived final : public base {
public:
    void member() override;
};
class error : public derived { };

input_line_9:1:15: error: base 'derived' is marked 'final'
class error : derived { };
              ^
input_line_8:6:8: note: 'derived' declared here
 class derived final : base {
       ^       ~~~~~
Interpreter Error:
  • final specifier can also be used on a virtual function to specify further overrides are not permitted
class derived2 : public base {
    void member() final;
};
class error : public derived2 {
    void member() override;
};

input_line_13:3:10: error: declaration of 'method' overrides a 'final' function
    void method() override;
         ^
input_line_11:2:10: note: overridden virtual function is here
    void method() final;
         ^
  • final makes it clear to the reader that there are no derived classes or methods
  • More importantly, final makes it clear to the compiler, allowing devirtualization optimizations

override¶

  • override ensures that the function overrides a virtual member function
    • Avoids potential mistakes and clarifies intent
class derived3 : public base {
    void member() const; // no error or warning
}
class derived4 : public base {
    virtual void member() const; // no error or warning
}
class derived5 : public base {
    void member() const override;
}

input_line_16:2:25: error: only virtual member functions can be marked 'override'
    void member() const override;
                        ^~~~~~~~

Construction¶

  • You can specify a default initializer for data members directly in the class definition
class example {
    int _a = 42;
    bool _b = false;
    string _c = "Hello World!";

public:
    friend inline ostream& operator<<(ostream& out, const example& x) {
       return out << "(" << x._a << ", " << x._b << ", " << x._c << ")";
    }
};
{
example x;
cout << x << endl;
}
  • Any constructor can override a default initializer
class example2 {
    int _a = 42;
    bool _b = false;
    string _c = "Hello World!";

public:
    example2() = default;
    example2(int a) : _a(a) { }

    friend inline ostream& operator<<(ostream& out, const example2& x) {
       return out << "(" << x._a << ", " << x._b << ", " << x._c << ")";
    }
};
{
    example2 x;
    cout << x << endl;
    example2 y(10);
    cout << y << endl;
}
  • Constructors can now delegate to other constructors
class example3 {
    int _a = 42;
    bool _b = false;
    string _c = "Hello World!";

public:
    example3(int a) : _a(a) { }
    example3(double a) : example3(static_cast<int>(round(a))) { }

    friend inline ostream& operator<<(ostream& out, const example3& x) {
       return out << "(" << x._a << ", " << x._b << ", " << x._c << ")";
    }
};
{
    example3 x(42.8);
    cout << x << endl;
}
  • Constructors can now be inherited
class example4 : public example3 {
public:
    string _d = "New Member";

    using example3::example3;
};
{
    example4 x(42.8);
    cout << x << endl;
    cout << x._d << endl;
}
  • Inheriting constructors is "all or nothing"
    • However, you can replace an inherited constructor
class example5 : public example3 {
public:
    using example3::example3;
    example5(int a) : example3(a + 1) { }
};
{
    example5 x(10);
    cout << x << endl;
}
  • Or delete an inherited constructor
class example6 : public example3 {
public:
    using example3::example3;
    example6(int a) = delete;
};
{
    example6 x(10);
    cout << x << endl;
}

input_line_27:3:14: error: call to deleted constructor of 'example6'
    example6 x(10);
             ^ ~~
input_line_26:4:5: note: 'example6' has been explicitly marked deleted here
    example6(int a) = delete;
    ^

static member variables¶

Review¶

  • non-const static members must be defined at namespace scope
namespace bcc {

struct example7 {
    static int x; // declaration
};

int example7::x = 5; // definition (don't put in a header!)

}
  • const static members may be initialized directly in the class
    • No definition is required unless odr-used
struct example8 {
    const static int x = 42;
};

(void)(cout << example8::x << endl); // not an odr-use

New¶

  • static members may be declared inline (C++17) and constexpr
    • inline static members do not require a definition at namespace scope
struct example9 {
    inline static int x = 42;
};

example9::x = 56;
  • constexpr static members, like const static members, only require a namespace scope definition if odr-used (until C++17)
struct example10 {
    constexpr static int x = 42;
};

(void)(cout << example10::x << endl); // not an odr-use
  • Since C++17, constexpr implies inline for static member variables
    • No definition is required even for an ODR use
(void)(cout << &example10::x << endl); // an odr-use

Recommendations¶

  • If you must use public inheritance
    • Use final and override as appropriate
  • Use member initialization, delegating, and inheriting constructors to simplify class definitions
  • Follow the recommendations for static variables for static member variables

Homework¶

  • Find code which does a cast to a derived class then makes virtual function calls
    • Mark the derived class as final
    • Inspect assembly before and after to see if the compiler is able to devirtualize the calls