7

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."

Thomas L Holaday
  • 13,614
  • 6
  • 40
  • 51
  • 2
    http://stackoverflow.com/questions/10985/how-to-prevent-an-object-being-created-on-the-heap – Hasturkun Jul 15 '09 at 18:49
  • 1
    @Hasturkun, 10985 solution fails objective 1. – Thomas L Holaday Jul 15 '09 at 19:07
  • 1
    This is not homework. This is "show that C++ is a language in which a programmer can express intent with accuracy and precision." – Thomas L Holaday Jul 16 '09 at 14:32
  • @Thomas: Can you explain the underlying problem you're trying to solve here? I get the impression from your previous comment that this is a hypothetical requirement which you just want to know if it can be done in C++, is that correct? – Richard Corden Jul 16 '09 at 15:53
  • @Richard There's a somewhat larger question as to whether "creatable-on-the-heap" is part of a class' behavior. One principle of the C++ type system is that derived classes cannot/ought not cancel parent class behaviors - I think Eiffel allows this, and that C++ does not is one of the distinctions between the two. Another question is what are the mechanisms that would allow a library author to constrain library users, at compile, link, test-unit, or lint time? The template mechanism is Turing complete. Is there some role for it in this area? – Thomas L Holaday Jul 17 '09 at 00:56
  • @Thomas: Your question is abstract and so it is impossible to find a perfect answer since you haven't got a problem to solve. Given that you require that base classes can be created on the heap, what you really appear to be looking for is that the "inheritance" has this extra property. – Richard Corden Jul 20 '09 at 07:28
  • @Richard, in what way is the question abstract? The requirements are definite: provide an implementation of Base such that any class derived from Base cannot be allocated on the heap without giving rise to a compile-, link-, or test-unit error, or lint gripe. The lint gripe is surely straightforward, because lint can check every invocation of operator new and guarantee that the object is not a subclass of Base. – Thomas L Holaday Jul 20 '09 at 14:02
  • @Thomas: It's abstract in the sense that although you've stated explicit requirements, these requirements don't appear to be modeling a real world concept. I asked this earlier and you replied asking if "createable-on-the-heap" was a property of the class or not. Is there a real world problem that you're trying to solve? If there is, then it may be possible to give you better answers. Frequently the correct answer to a question is a design change at a higher level, making the original question moot. – Richard Corden Jul 20 '09 at 16:23
  • @Richard, a higher-level design change which, when implemented, meets objectives 1 and 2, would be a satisfactory solution; and so would a proof that, within the limits of C++ as it is currently defined, no solution exists which will flag a violation of objectives 1 and 2 at compile, link, or test-unit time (since I believe there is general agreement that the violation could be flagged by a sufficiently-advanced lint). – Thomas L Holaday Jul 20 '09 at 21:52
  • @Thomas: So let me try one more time: "What high level problem are you trying to solve that means you require objectives 1 or 2?" If, as I'm beginning to believe, this is purely a "can this be done" type question, then I think I'll let others spend their time trying to help you. – Richard Corden Jul 21 '09 at 09:26

5 Answers5

5

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
  • 198,619
  • 38
  • 280
  • 391
  • +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 will not compile. – Thomas L Holaday Jul 15 '09 at 19:16
  • 1
    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 Jul 16 '09 at 10:07
  • 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 Jul 16 '09 at 14:08
  • 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 Jul 16 '09 at 14:55
  • 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 Jul 20 '09 at 07:25
4

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".

Community
  • 1
  • 1
Richard Corden
  • 21,389
  • 8
  • 58
  • 85
0

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 .

Jason Plank
  • 2,336
  • 5
  • 31
  • 40
ryansstack
  • 1,396
  • 1
  • 15
  • 33
  • How does this prevent `Derived` from being allocated on the heap, if somebody calls `new Derived` directly? – ephemient Jul 15 '09 at 18:48
  • 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 Jul 15 '09 at 19:01
  • 1
    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 Jul 15 '09 at 19:17
0

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.

Sam Harwell
  • 97,721
  • 20
  • 209
  • 280
  • 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 Jul 16 '09 at 14:45
0

how about privately derive Base?

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

class Derived : private Base {
public:
};
newbie
  • 71
  • 2