开发者

How to design a class where the user/caller has options to provide a custom behavior using a custom class

I encounters a problem where I want to have a class in which its behavior can be customized by another class, for example, Foo's constructor accepts a parameter of some type of class:

class Bar { //The default class that define behavior
};

template <typena开发者_运维问答me T = Bar>
class Foo {
  public:
    Foo(T* t = 0) t_(t) {
      if (t_ == 0) t_ = new T();
    }
    ~Foo() { 
      delete t_;
    }
}

Now if someone use Foo in a client code:

Foo foo;

Everything is fine. But, if we want to supply the custom class:

class Bar1 { };

Foo<Bar1> foo(new Bar1()); // This is OK
Bar1 b;
Foo<Bar1> foo(&b); // Error, b is not dynamically allocated

Is there any design pattern I can use to prevent this kind of mistakes? Or, is there any techniques or semantics where the user of Foo class can choose/specify who owns the bar object? So for example, the above Foo destructor can be like this:

    ~Foo() { 
      if (t_ is owned by this object) delete t_;
    }

Bar or Bar1 or any class passed as t in Foo(T* t) might be a big object, so if it is possible I rather not to pass it by value.

Update: What I have in mind is for the user to be able to do something like:

Foo foo(new Bar(1, 2, etc..));
//or this:
Bar bar(1, 2, etc..);
Foo foo(bar);

But if bar is a big object (for example, contains an array), then it would be inefficient to pass bar by value. The only way is to have bar passed by reference or pointer, but I also want the user to be able to use bar with parameterized constructor, and hence my confusion began. Regarding to the auto variable scope, it would be safe if a user do something like:

int main() {
  Bar1 bar1(1,2,3);
  Foo foo(&bar1);
  return 0;
}

As long as Foo does not delete bar1.


You can't detect the difference between stack and heap allocation (the standard doesn't even mention a stack), but that's not the issue here.

Foo should not be deleting things that it does not own. If it wants a copy for itself then it should just make a copy:

template <class T = Bar>
class Foo
{
  T t_;
public:
  Foo() {}
  Foo(const T& t) : t_(t) {}
};

Or if you need it to be a pointer:

template <class T = Bar>
class Foo
{
  T* t_;
public:
  Foo() : t_(new T()) {}
  Foo(const T& t) : t_(new T(t)) {}
};

You can't just go around deleting things that people give you, and that's regardless of whether you know if it's stack or heap allocated. What if your Foo goes and deletes it but the calling code still wants to use it? Or what if that same object is passed into two of your Foo objects and they both delete it?

Your option is either to make a copy, or not delete it.

An alternative to doing the copy would be to mark whether you are using your own or someone else's:

template <class T = Bar>
class Foo
{
  T* t_;
  bool owned;
public:
  Foo() : t_(new T()), owned(true) {}
  Foo(T* t) : t_(t), owned(false) {}
  ~Foo() { if (owned) delete t_; }
};

In case you are wondering, t is passed by const-ref in my previous solutions, not by value, so there is no excessive expense, although there could be an expense in the copying.


Use a smart pointer and you won't have to worry about memory management. If you really have to, I guess you should have the user of your class handle the memory they pass in, your class should be only responsible for deleting the objects that it created.

You have to consider if you really need to use pointers. A simple design like this one:

template <typename T = Bar>
class Foo1 {
  T t_;
  public:
    Foo1() {
    }
    Foo1(const T& t) : t_(t) {
    }
};

might be all you need. There's nothing wrong with using it like this:

int main() {
  Bar1 bar1(1,2,3);
  Foo1 foo(bar1); // specify custom behavior
  Foo1 foooo; // get default behavior
  return 0;
}

If you really need pointers, Dan provided a good answer using std::auto_ptr<>. You can guarantee you always have an object created with something like:

template <typename T = Bar>
class Foo2 {
  std::auto_ptr<T> t_;
  public:
    Foo2() : t_(std::auto_ptr<T>(new T)) { }
    Foo2(std::auto_ptr<T> t) : t_(t) {
      if (t_.get() == 0) t_ = std::auto_ptr<T>(new T);
    }
};

Then you can call it like this:

std::auto_ptr<Bar1> s(new Bar1());
Foo2<Bar1> foo(s);
Foo2<Bar1> foooo;

As for the user passing the address of a stack object to a smart pointer, that's really the user's fault for doing something wrong. The program will crash as soon as the auto_ptr tries to delete the object.


Short answer, No.

For any arbitrary type T to be used such as Bar1 then you have to simply accept that your class's users may write code that explodes.

However if you only construct the instance of type T when you construct the class, instad of taking a pointer to an instance you could in Foo's code construct the instance of Bar1 using a Create function. But then you can't pass arguments to the constructor.

class Bar
{
  public: static Bar * Create()
}

template <typename T>
class Foo
{
  public: static Foo<T>* Create() { return new Foo<T>(T::Create()); } 
}


I can in now way endorse this for anything but pure fun, but here is a class that tests where it was allocated:

class AllocTester {
public:
   bool on_stack() {
      AllocTester outer;
      return on_stack_check(&outer);
   }
private:
   bool on_stack_check(AllocTester *outer) {
      AllocTester inner;
      return (outer < &inner) == (this < &inner);
   }
};

This won't work with multithreading, static variables or unusual architectures. So if you try to use this for any "real" coding you will just break you application. Only look at it as curiosity, in real life the caller of your class should be responsible to handle the allocated memory correctly.

Usually if there is a function that allocates memory, there should be a corresponding function that frees it again. If the class takes ownership of the passed pointer this should be made explicitly clear. Maybe even using auto_ptr would be a good idea since it explicitly transfers ownership of the pointer to the constructor.


To clarify my comments in Poita_'s answer:

template <typename T = Bar> 
class Foo { 
    T& t_;
    std::auto_ptr<T> pT_; 

public: 
    Foo(T& t) : t_(t) { 
    }
    Foo(std::auto_ptr<T> pT) : pT_(pT), t_(*pT) { 
    } 

    T& getT() const {
        T* pT = pT_.get();
        if (pT != NULL)
            return *pT;
        return t_;
    }
};

You can then use it as follows:

std::auto_ptr<Bar> pBar(new Bar());
Foo<Bar> foo0(pBar);
Bar& b0 = foo0.getT();

Bar b;
Foo<Bar> foo1(b);
Bar& b1 = foo1.getT();

As this code shows, you should (almost) always place the result of new into some object which manages the memory. std::auto_ptr<> will pass ownership of the pointer to Foo. Alternatively, you could pass the pointer directly in the constructor, but that makes it easier to call Foo(&bar).

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜