tags:

views:

967

answers:

5

Objectives:

  1. Objects of class Base may be static, automatic, allocated directly on the heap, and take part in composite objects allocated anywhere
  2. For any class Derived which has Base as an accessible ancestor, objects may be static or automatic and take part in composites, but may not be allocated directly on the heap

Example:

#include "Base.h"
#include "Derived.h"

{
    static Base sb;              // OK
    Base ab, *hb = new Base;     // OK
    static Derived sd;           // OK
    Derived ad;                  // OK
    Derived *pd = &ad;           // OK
    Derived *hd = new Derived;   // Compile error, link error, 
                                 // test-unit exception, or lint gripe
    struct Composite {
        Base cb;
        Derived cd;
    } *hc = new Composite;       // OK 

    // Edit to show side-effects of solutions which hide Base::operator new.

    std::vector<Base> vb;        // OK 
    std::vector<Derived> vd;     // Error
    // ...
}

How could Base be implemented to achieve this? A compile-time error is preferred to a link-time error; but although both are preferred to a test-unit exception, and a test-unit exception is preferred to a lint gripe, any solution that does not require an update to Base.h for each derivation will do.

Edit: For the purposes of this question, solutions which involve forking the compiler so that it supports arbitrary decorations are, despite the technical challenge, classified as "trivial."

+5  A: 

Hmm, Eclipse's answer is gone, but I thought it was on the right track.

class Base {
public:
    static Base *create() { return new Base; }
    static Base *create(size_t n) { return new Base[n]; }
private: 
    // Prevent heap allocation
    void *operator new(size_t s);
    void *operator new[](size_t s);
};

This isn't nice, as it obligates you to use Base::create() instead of new Base, and class Derived could still go and implement its own public operator new, but I think it's workable.

ephemient
+1 for thinking outside the box, although the sample code permits new Base and Derived::operator new is a threat. It has the drawback of making Base ineligible for much of the standard template library: vector<Base> will not compile.
Thomas L Holaday
You have the private operate new in the wrong class - you should have it in Derived and then the behaviour matches the requirements. (+1 for being 99% there).
Richard Corden
You'd have to add it in every class that derives from `Base`, but yeah, that would allow `new Base`. I couldn't tell from my reading of the question whether that was acceptable or not.
ephemient
The desired result is a mechanism which the author of Base can provide for users unknown and remote which will provide tool support for the two listed objectives.
Thomas L Holaday
I've just realised that this does not fully address the problem. If the derived class implements it's own versions of the operators then it can still be created on the heap.
Richard Corden
A: 

Here's one solution I found on the web:

Another standard new, the so-called "placement-new," doesn't allocate memory at all, but can be called to invoke an object's constructor on an arbitrary piece of memory. It is typically defined as:

inline void * operator new(size_t, void *p) { return p; }

From http://www.scs.stanford.edu/~dm/home/papers/c++-new.html .

ryansstack
How does this prevent `Derived` from being allocated on the heap, if somebody calls `new Derived` directly?
ephemient
What this does is just return the memory address p as your "new" object. If you have allocated p on the stack as a large buffer (char p[200]), then use new (p) Derived(...), your Derived object isn't allocated on the heap. I suppose your question is, what if I don't use new (p) Derived... I don't know if we can get away from declaring operator new as private in the subclass.
ryansstack
I'm still reading up on this though, interesting question.
ryansstack
Here's my final comment, overload new(size_t) in the subclass to always call new (size_t, p), and make sure p is manually working. This'll throw an exception if p isn't managed and new Derived is called, and it will probably let you allocate new structs containing Derived classes... now I gotta go try this... Don't expect any results posted, since we've never heard if this is homework or not.
ryansstack
A: 

While there are ways to "discourage" heap allocation of an object in code (several good ones discussed and linked to here), there is no way to fully prevent it. There are some types such as shared_ptr<T> that I generally never want heap allocated (or perhaps a variant that I truly never want heap allocated). For these, the absolute best solution is understanding and documenting why you never want them heap allocated. With that in place, we never ran into problems because we were consistent about it.

280Z28
Documented coding conventions are good; C-with-Classes originated in documented coding conventions. Tool support for coding conventions, for example the "private" keyword in the compiler or a rule "operator new for classes derived from Base is an error" in lint reduce the risk that a name will be misused by a programmer whose understanding of the problem domain is far greater than his understanding of the library documentation.
Thomas L Holaday
A: 

how about privately derive Base?

class Base {
public:
    void *operator new(size_t); 
};

class Derived : private Base {
public:
};
newbie
+3  A: 

I feel bad for stealing ephemient's thunder here, but the only thing "wrong" with his answer is that he's made Base's operator new private, and not Derived's:

The following code compiles except for the last line, which I think is what you require:

#include <new>
#include <vector>

class Base {};

class Derived : public Base {
private:
  void * operator new (size_t);
};

void foo ()
{
    static Base sb;              // OK
    Base ab, *hb = new Base;     // OK
    static Derived sd;           // OK
    Derived ad;                  // OK
    Derived *pd = &ad;           // OK

    struct Composite {
        Base cb;
        Derived cd;
    } *hc = new Composite;       // OK 

    std::vector<Base> vb;        // OK 
    std::vector<Derived> vd;     // OK


    Derived *hd = new Derived;   // Compile error
}

UPDATE:

As Tal points out, you can still call "new Derived" from a static member of Derived, however by not defining the "operator new" this will result in a link error.

But you can change the code slightly so that it generates a compile error (which is always preferable). We can declare a placement operator new that will still stop us calling the usual operator new.

class Derived : public Base {
public:
  static Derived * create ()
  {
    return new Derived;
  }

private:
  class dont_dynamically_allocate_type_derived;
  void * operator new (size_t, dont_dynamically_allocate_type_derived);
};

Using g++, the the above generates:

t.cc: In static member function static Derived* Derived::create():

t.cc:10: error: no matching function for call to Derived::operator new(unsigned int)

t.cc:15: note: candidates are: static void* Derived::operator new(size_t, Derived::dont_dynamically_allocate_type_derived)

UPDATE (part duex):

I cannot think of any construct where a base class propagates a property that it itself doesn't also have. Tweaking the requirements slightly, if it's allowed to add an extra layer of inheritance you can create two leaf classes, one for Base the type that will be used to instantiate Base objects, and the other that is inherited from:

class Base
{
public:

private:
  friend class BaseInstance;
  friend class BaseDerived;
  ~Base () {}
};

class BaseInstance : public Base
{
public:
  ~BaseInstance () {}
};

class BaseDerived : public Base
{
public:
  ~BaseDerived () {}

private:
  static void * operator new (size_t);
  static void * operator new[] (size_t);
};

class Derived : public BaseDerived {
public:
  static Derived * create ()
  {
    return new Derived;
  }
};

There is still a problem that people can derive from "BaseInstance", although it's name could be made explicitly to stop that happening. There's also the possibility that that class is on the other side of an API and so clients only ever get to see "BaseDerived".

Richard Corden
You could still call "new Derived" from a static method in Derived. Not sure if that's a problem for the OP, though.
Tal Pressman
You also have to do this for every class that derives from `Base`, and I can't tell if OP finds that acceptable or not.
ephemient
A solution which allows the author of Base to enforce the rule on all derived classes is preferred.
Thomas L Holaday