4

Say you have a base class Dep for a tree of classes. There is a virtual method Dep* Dep::create() that I want to be implemented by every single leaf class. Is there any way to enforce this?

Note: The problem here is that there could be intermediate classes (say class B : public A : public Dep) implementing this method (A::create) by accident or because they think they are leaf classes, but are in fact subclassed themselves.

The question ends here.

Context

If you are curious why I need this; I have a class Master which has Dep objects of unknown concrete type. If Master is duplicated, I need to come up with a matching clone of the Dep instance. Next best thing to do is the virtual constructor idiom, which introduces precisely this problem.

Additionally, I cannot even catch this (other then by crashing horribly), because for obscure reasons, people that have more to say than me, have outlawed dynamic_cast in this project (perhaps this is a good decision; But anyways a completely different discussion).

bitmask
  • 32,434
  • 14
  • 99
  • 159
  • When it crashes, is the stack trace consistent? – Tom Kerr Sep 02 '11 at 20:17
  • @Tom K: What I meant by this was: I have to `static_cast` my `Dep` pointers into what I *believe* they are. So if they would be created with the wrong type, I would cast into something that is most likely totally incompatible (a different leaf). – bitmask Sep 02 '11 at 20:28
  • Hm, is it an option to demand that every class inherit from some sentinel class? If you had `class T : public Base, public CloneTest`, then `CloneTest` could contain a typedef for `declspec(this->T::clone())`, which would fail (?) if there were no override. – Kerrek SB Sep 02 '11 at 20:33
  • @Kerrek SB: Doesn't that merely move the problem to enforcing that you inherit from `CloneTest` or am I missing something? – bitmask Sep 02 '11 at 20:36
  • @Kerrek also "demand" doesn't really do much. – Seth Carnegie Sep 02 '11 at 20:42
  • Yea, which should be avoided by redesigning probably. If it's just the way it is, then you should try to make it hard for them to do it accidentally. If you fail fast by seg fault then it wont leak out into production code unless they are willfully negligent. If the stack trace is consistent you can leave a comment where they will find it. – Tom Kerr Sep 02 '11 at 20:43
  • @bitmask: Yes, it would just be shifting the problem, but it would shift it to a place you could check more easily with `grep`... – Kerrek SB Sep 02 '11 at 20:49
  • I had asked [similar question](http://stackoverflow.com/questions/9477581/force-all-classes-to-implement-override-a-pure-virtual-method-in-multi-level) recently. You can visit it to check if it helps you. – iammilind Apr 05 '12 at 07:42

5 Answers5

4

C++ provides no way to keep a class from inheriting from your class, and there is no way to make a particular class in the inheritance hierarchy implement a method. The only rule is that somewhere in the inheritance hierarchy above a particular class (not necessarily in the leaf) all virtual functions must have an implementation for that class to be instantiatable.

For instance, A could inherit from Def and implement all it's [pure] virtual methods. Then if B inherits from A, it doesn't have to implement anything. There's no way to keep that from happening.

So the answer is no, there is no way to enforce this.

Seth Carnegie
  • 73,875
  • 22
  • 181
  • 249
  • Yes, I am aware of that. Hence the question. – bitmask Sep 02 '11 at 20:23
  • 4
    @bitmask you knew there was no way to do it, why ask? This is the answer to your question: no. – Seth Carnegie Sep 02 '11 at 20:24
  • No, I am aware how inheritance works (so my statement related to your second paragraph). Also I knew that *to my knowledge the answer is no*, but I really hoped a smarter person than me would come up with a solution. – bitmask Sep 02 '11 at 20:33
  • 1
    @bitmask I too hope someone smarter than us will come up with a solution. I highly doubt it though. – Seth Carnegie Sep 02 '11 at 20:42
  • Somehow it violates the OOP principle, so *maybe* it's a good thing but we'll see. :) – bitmask Sep 02 '11 at 20:49
  • 2
    @Seth - although C++ does not provide a language feature specifically to prevent subclassing, there are ways to do it -- see http://www.parashift.com/c++-faq-lite/strange-inheritance.html#faq-23.11 . – Andy Thomas Sep 02 '11 at 21:05
3

Using curiously recurring template fun, you can achieve something quite similar:

template<typename T>
class Cloneable : public T, public Dep
{
private:
    Cloneable<T>() : T() { }
public:
    static Cloneable<T>* Create() { return new Cloneable<T>(); }
    Cloneable<T>* clone() { return new Cloneable<T>(*this); }
};

Instead of deriving from Dep and instantiating via new MyType, use Cloneable<MyType>::Create. Since Cloneable<MyType> is derived from MyType, you can use the instance the same way you would use any MyType, except that it is now guaranteed to have Dep::clone.

Additionally your Master should not accept an instance of type Dep, but enforce that it is a Cloneable<T>. (Replace your orignial function by a simple function template that enforces this.) This guarantees that any Dep inside the master has a correctly implemented clone function.

Since Cloneable<MyType> has no public constructor, it cannot be inherited, however your actual MyType can be further inherited and used just as before.

danielschemmel
  • 10,885
  • 1
  • 36
  • 58
  • I implemented a very similar approach now, but what you call `T` is still the leaf class but carries a `Cloner` object (and *is* `Clonable`, inherited from the `Dep` base). I did this for certain secondary constraints ... still, your answer is closer to my original question. Man, why didn't *I* think of this? – bitmask Sep 03 '11 at 22:57
2

Did TPTB outlaw all RTTI, or only dynamic_cast<>()? If you can use RTTI, then you can enforce the existence of the method as a postcondition of calling it:

#include <typeinfo>
#include <cassert>
#include <iostream>
#include <stdexcept>

class Base {
protected:
  virtual Base* do_create() = 0;
  virtual ~Base() {}
public:
  Base* create() {
    Base *that = this->do_create();
    if( typeid(*this) != typeid(*that) ) {
      throw(std::logic_error(std::string() +
                             "Type: " +
                             typeid(*this).name() +
                             " != " +
                             typeid(*that).name()));
    }
    return that;
  }
};

class Derive1 : public Base {
protected:
  Base* do_create() { return new Derive1(*this); }
};

class Derive2 : public Derive1 {};

void f(Base*p) { std::cout << typeid(*p).name() << "\n"; }
int main() {
  Derive1 d1;
  Base *pD1 = d1.create(); // will succeed with correct semantics
  Derive2 d2;
  Base *pD2 = d2.create(); // will throw exception due to missing Derive2::do_create()
}
Robᵩ
  • 163,533
  • 20
  • 239
  • 308
  • Unfortunately (or should I say fortunately?) all RTTI is deactivated, not only `dynamic_cast`. But nice approach, nevertheless! – bitmask Sep 02 '11 at 20:58
  • 2
    +1 - Darn it, that's the same idea I had. This is using NVI, the Non-Virtual Idiom, which allows the base class to attach post-conditions. Although it doesn't satisfy the title question, it does satisfy the specific context cited by the poster -- if it weren't for that pesky complete absence of RTTI. :) – Andy Thomas Sep 02 '11 at 21:14
  • 1
    @bitmask if you want to use this approach and if you have access to all builds for subclasses, you could simply use this approach in an independent test build where RTTI is enabled to verify everything is implemented properly. then the check would simply not be made in the regular build. – justin Sep 03 '11 at 06:59
2

If you control the base class AbstractDep then you can enforce that concrete leaf classes must be created by using a class template WithCloning. This leaf can then be sealed so that it cannot be inherited. Or more precisely, instances cannot be created of a derived class.

class AbstractDep
{
template< class Type > friend class WithCloning;
private:
    enum FooFoo {};
    virtual FooFoo toBeImplementedByLeafClass() = 0;

public:
    virtual AbstractDep* clone() const = 0;
};

template< class Type > class WithCloning;

class Sealed
{
template< class Type > friend class WithCloning;
private:
    Sealed() {}
};

template< class Type >
class WithCloning
    : public Type
    , public virtual Sealed
{
private:
    AbstractDep::FooFoo toBeImplementedByLeafClass()
    {
        return AbstractDep::FooFoo();
    }

public:
    virtual WithCloning* clone() const
    {
        return new WithCloning( *this );
    }
};

typedef WithCloning<AbstractDep>            Dep;


class AbstractDerivedDep
    : public AbstractDep
{
// AbstractDep::FooFoo toBeImplementedByLeafClass(); // !Not compile.
public:
};

typedef WithCloning<AbstractDerivedDep>     DDep;


struct Foo: Dep {};       // !Does not compile if instantiated.

int main()
{
    Dep     d;
    //Foo     f;
}

If the classes require more than default construction then that most be solved additionally.

One solution is then to forward an argument pack from the WithCloning constructor (there is an example C++98 implementation on my blog, and C++0x supports that directly).

Summing up, to be instantiable the class must be WithCloning.

Cheers & hth.,

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
0

when you say that they are unknown, i presume they still inherit from a common base class /interface right?

only thing i can think of you can use to force is on the virtual base class add

virtual Base* clone() {
    assert( 1==0 ); //needs to be overriden
}

so you are forced to override, but this is only detected at run-time when trying to call clone on a instance of a class that is missing the override

even if you are not allowed to use dynamic_cast or RTTI, you can still enable it for debug purposes locally on your build, if that will help you find the typeid of offending classes

it sounds like you are familiar with the clone pattern, but i'll post it quietly in here, and we can forget about it: http://www.cplusplus.com/forum/articles/18757/

lurscher
  • 25,930
  • 29
  • 122
  • 185
  • 4
    This doesn't force leaf classes to override `clone`. The only thing this does is about the same as making it pure virtual. – Seth Carnegie Sep 02 '11 at 20:23
  • Yes, they all inherit from `Dep`. Your method is essentially a non-enforced pure virtual method. So, intermediate classes could still implement methods *for their children*. – bitmask Sep 02 '11 at 20:30