8

I have an abstract base class:

struct Base : std::enable_shared_from_this<Base> 
{
    virtual ~Base() = default;
    virtual void foo() = 0;

    void bar() {
        baz(shared_from_this());
    }
};

The only valid use case for Base is to live in a shared_ptr - bar is an important method. How can I ensure that the following is impossible:

struct BadDerived : Base {
    void foo() override { ... }
};

BadDerived bd;
bd.bar(); 
Barry
  • 286,269
  • 29
  • 621
  • 977
  • Have you though about using a non virtual interface? http://stackoverflow.com/a/2735596/4342498 – NathanOliver Apr 16 '15 at 19:18
  • If there were a way to do this, `enable_shared_from_this` already would do it, and your class would get it for free. – Ben Voigt Apr 16 '15 at 19:23
  • @vsoftco I don't want to be able to construct `Base` outside of a `shared_ptr` so that `bar()` can't be called outside of a `shared_ptr`. – Barry Apr 16 '15 at 19:26
  • Is making the constructor private and providing a factory function not an option? – imreal Apr 16 '15 at 19:34

2 Answers2

5

One technique is to make the constructor of Base private and friend a factory class or method:

struct Base : std::enable_shared_from_this<Base> 
{
    virtual ~Base() = default;
    virtual void foo() = 0;

    void bar() {
        baz(shared_from_this());
    }

private:
    template<class Impl> friend std::shared_ptr<Base> makeDerived();
    Base() {}
};

template<class Impl>
std::shared_ptr<Base> makeDerived() {
    struct Derived : Base, Impl {
        void foo() override { Impl::foo(static_cast<Base*>(this)); }
    };
    return std::make_shared<Derived>();
}

Usage:

struct Impl {
    void foo(Base* self) { std::cout << "Hello!" << std::endl; }
};
auto gd = makeDerived<Impl>();
gd->bar();

This does require you to rewrite any existing derived classes.

ecatmur
  • 152,476
  • 27
  • 293
  • 366
0

Building off of ecatmur's answer, we could also make Base constructible from a type that only has a private constructor:

class PrivateT {
    PrivateT() { }

    template <typename Impl, typename... Args>
    friend std::shared_ptr<Impl> makeDerived(Args&&... );
};

struct Base : std::enable_shared_from_this<Base> {
    Base(PrivateT ) { }
    virtual void foo() = 0;

    void bar() {
        baz(shared_from_this());
    }
};

template <typename Impl, typename... Args>
std::shared_ptr<Impl> makeDerived(Args&&... args) {
    return std::make_shared<Impl>(std::forward<Args>(args)...,
        PrivateT{});
}

Every Derived type will have to take an extra constructor argument of type PrivateT that it will have to forward through... but it will still be able to inherit from Base!

struct Impl : Base {
    Impl(PrivateT pt) : Base(pt) { }
    void foo() override { std::cout << "Hello!" << std::endl; }
};

auto gd = makeDerived<Impl>();
gd->bar();
Community
  • 1
  • 1
Barry
  • 286,269
  • 29
  • 621
  • 977
  • why not use the handle body idiom? if you say it only makes sense to be a shared pointer, instead of a friend function use a constructor. and have the object the shared pointer as composition. that way nobody sees the shared pointer and you can enforce this policy. – Alexander Oh Apr 17 '15 at 17:24
  • http://ideone.com/41O3s7 played around with it. the main thing it should make is keep people from typing `x = *Base` because the pointer is inaccessible. but it's merely syntactic sugar. – Alexander Oh Apr 17 '15 at 18:28
  • I wanted to fold the factory function into the same class as the `shared_ptr` and using the class constructor as the factory function. But this does only work if the constructor call without `<>` is unique, because `BaseHandle()` is not an allowed constructor call. – Alexander Oh Apr 17 '15 at 18:35