[C++] Automatic generation of create()/destroy() functions

Started by
25 comments, last by loufoque 15 years, 3 months ago
Quote:Original post by Gage64
... this is for situations where you have to create the object after doing several operations so you can't use the initializer list.


There is no such situation, unless you have very poorly designed and implemented code. Coming up with a design pattern to support and obfuscate the use of poorly designed or implemented code is not something I would condone with any great degreee enthusiasm.

Now, consider this as a better implementation of your example. It compiles and runs.
  #include <iostream>  using namespace std;  class A {  public:    A(int) {}  };  struct UserData  {    UserData() {        cout << "Enter data" << endl;        int data;        cin >> data;    }    int data;  };  class B {  public:    B()    : a(UserData().data)    { }  private:    A a;  };  int main()  {    B b;  }

Stephen M. Webb
Professional Free Software Developer

Advertisement
Quote:Original post by KulSeran
I think the RAII post is more to the point of:


In that example the lock will be released at the end of the outer scope, so A is not locked (but B and C are).

But why did you create the holder in the outer scope and initialize it in the inner scope? Why would you even want to use it in this case? A simple stack allocated object will suffice.

Quote:Original post by Bregma
Quote:Original post by Gage64
... this is for situations where you have to create the object after doing several operations so you can't use the initializer list.

There is no such situation, unless you have very poorly designed and implemented code.


Well I can't think of an example off the top of my head, but are you saying there's never a situation where you have to do something like this:

class B {public:    B(/*whatever*/);};class A {public:    A() {        // Do some things that will "generate" the data needed to initialize B        // ...        // Now initialize B        b = new B(/*the data generated in the previous steps*/);    }private:    B *b;};


Quote:Now, consider this as a better implementation of your example.


Why is that better?
Quote:Original post by Gage64
Well I can't think of an example off the top of my head, but are you saying there's never a situation where you have to do something like this:
*** Source Snippet Removed ***
That is a job for std::auto_ptr - no need for your own handle class.

Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]

It isn't. You probably should throw an exception if cin fails, since otherwise data is just uninitialized. (And it also writes to a local data.)

However, you could do input in a function that returns the entered value (and throws if input fails.) Probably in all cases you can either get the data to be used for initialization from a function. Or compute it first and provide a suitable constructor.

But here's your original class in somewhat better style as I believe.

Main changes: assignment operator shouldn't freak out if one or both are empty holders, since it is a valid state for this class. destroy is a private method, since the user doesn't really need it (the example shows how you can reset a holder earlier if you are desperate). asserts should be done in the place where it would be an error to continue (that's the casts in getObj) - for this reason code had to be reordered in the destroy method.

#include <cassert>// Note: Currently supports objects with zero, one or two parameter c'torstemplate <class T>class Holder {public:    Holder():created(false)    {    }    ~Holder()    {        destroy();    }    Holder(const Holder<T> &other): created(false)    {        if (other.created) {            create(other.getObj());        }    }    Holder<T> & operator=(const Holder<T> &other)    {        if (this == &other) {            return *this;        }        if (!other.created) {            destroy();        }        else if (!created) {            create(other.getObj());        }        else {            getObj() = other.getObj();        }        return *this;    }    T* operator->()    {        return &getObj();    }    const T* operator->() const    {        return &getObj();    }    T & operator*()    {        return getObj();    }    const T & operator*() const    {        return getObj();    }    void create()    {        new (obj) T;        created = true;    }    template <class P1>    void create(const P1 &p1)    {        new (obj) T(p1);        created = true;    }    template <class P1, class P2>    void create(const P1 &p1, const P2 &p2)    {        new (obj) T(p1, p2);        created = true;    }private:    T& getObj()    {        assert(created && "Holder holds no instance");        return *(reinterpret_cast<T*>(obj));    }    const T& getObj() const    {        assert(created && "Holder holds no instance");        return (const_cast<Holder<T>*>(this))->getObj();    }    void destroy()    {        if (created)        {            getObj().~T();            created = false;        }    }    char obj[sizeof(T)];    bool created;};#include <iostream>#include <string>using namespace std;struct Student {    string name;    int grade;    Student(const string &name, int grade)        : name(name), grade(grade)    {        cout << "Student::Student() for " << name << endl;    }    ~Student() {        cout << "Student::~Student() for " << name << endl;    }    void print() const {        cout << name << ", " << grade << endl;    }};int main(){    Holder<Student> st1;    Holder<Student> st2;    cout << "Hello" << endl;    st1.create("John", 87);    st2.create("Mike", 92);    st1->print();    (*st2).print();    st2 = st1;    st2->print();    st1 = Holder<Student>();    cout << "Goodbye" << endl;}
Quote:Original post by swiftcoder
Quote:Original post by Gage64
Well I can't think of an example off the top of my head, but are you saying there's never a situation where you have to do something like this:
*** Source Snippet Removed ***
That is a job for std::auto_ptr - no need for your own handle class.


Yet again, the difference is that my class doesn't use dynamic memory allocation.

Quote:Original post by visitor
here's your original class in somewhat better style as I believe.


Thanks, I like your changes, though I do think destroy() should be public, because it lets you destroy an object before the end of the scope. With your approach you can only do that by creating a new object.

EDIT: BTW, I think the assert in the const version of getObj() is redundant.
Quote:Original post by Gage64
Quote:Original post by swiftcoder
Quote:Original post by Gage64
Well I can't think of an example off the top of my head, but are you saying there's never a situation where you have to do something like this:
*** Source Snippet Removed ***
That is a job for std::auto_ptr - no need for your own handle class.
Yet again, the difference is that my class doesn't use dynamic memory allocation.
How so? Your create function calls new, so you are using dynamic allocation. That you hide the dynamic allocation from the user doesn't make it any better/faster/whatever.

Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]

It's placement new, which is called to construct the object in a particular place in memory (in this case a char array which is not dynamically allocated).

I don't see myself using this often but perhaps it isn't entirely meaningless. It is a way to postpone the creation of stack objects. It is not hard to postpone creation of objects if you allocate them dynamically, this is a way to do the same for stack instances.

---------

Perhaps you should also provide a constructor of the form

    Holder(const T& val)


so that this works

    Holder<X> h;    //...    h = X(a, b, c);


and you could get rid of the create method together with its limitation.

This might have the overhead of creating a temporary but then perhaps the compiler might be able to optimize that away.
I can see myself using something like this every once in a while. Usually, it's when I want to construct a by-value object with the results of some computation with the construction arguments.

Once, in my very early days of game programming, I was making a pong clone. I had multiple balls, and I wanted to have the color of the balls alternate, based on a static counter of the number of balls. I had some surface abstraction in the ball class by value, and I had to use a ternary expression and a macro that found out whether the number of balls was even. It would have been nice to have some way to delay the construction of that object.

I've come across a few such situations since. I've always gotten around it, but still...
I can't see the advantage of this over a nullable pointer or std::auto_ptr. Actually, auto_ptr's wacky assignment semantics seem tailor-made for this (and for very little else).

void main() {    auto_ptr<Student> st1;    auto_ptr<Student> st2;    cout << "Hello" << endl;    st1 = auto_ptr<Student>(new Student("John", 87));    st1 = auto_ptr<Student>(new Student("Mike", 92));    st1.create("John", 87);    st2.create("Mike", 92);    st1->print();    (*st2).print();    st2 = st1; // this one's a bit wonky, but you can't have everything    st2->print();    st1.release();    st1.release();  // Does nothing    // No call to st2.release();    cout << "Goodbye" << endl;}
Why not use boost::optional?
"Walk not the trodden path, for it has borne it's burden." -John, Flying Monk

This topic is closed to new replies.

Advertisement