0

Problem: I have a non-copyable object with two constructors. I need to create an object with one of the constructors and then use it within some common code:-

With a copyable object it would look like this, and be easy:

Object a;

if (condition) 
   a = Object(p1);
else
   a = Object(p2,p3,p4);

a.doSomething();

But, the object is non-copyable, so I've had to do this:

boost::scoped_ptr<Object> a;

if (condition) 
   a = new Object(p1);
else
   a = new Object(p2,p3,p4);

a->doSomething();

This feels too complex. Is there a better solutiuon?

curiousguy
  • 7,344
  • 2
  • 37
  • 52
Roddy
  • 63,052
  • 38
  • 156
  • 264

7 Answers7

4

Here's a very terrible hack, assuming Object is default-constructible:

Object a;
a.~Object();

if (condition) { ::new (&a) Object(p1); }
else           { ::new (&a) Object(p2, p3, p4); }

Don't use this.

Another option is using a union, but you'll need to invoke the destructor manually in that setup as well.


A cleaner solution could be achieved with Boost.Optional (using in-place factories). (Thanks to @K-Ballo for digging up the details!)

#include <boost/optional.hpp>
#include <boost/utility/in_place_factory.hpp>

struct Object
{
    explicit Object(int) {}
    explicit Object(int, float, std::string) {}

    Object(Object const &)             = delete;
    Object(Object &&)                  = delete;
    Object & operator=(Object const &) = delete;
    Object & operator=(Object &&)      = delete;
};

boost::optional<Object> a;

if (condition) { a = boost::in_place(0); }
else           { a = boost::in_place(0, 1.0f, "two" ); }
Community
  • 1
  • 1
Kerrek SB
  • 428,875
  • 83
  • 813
  • 1,025
  • 1
    Optional has some old form of emplacement, with an in place flag or something similar – K-ballo Jan 18 '13 at 18:18
  • 1
    @K-ballo: You're right, [in-place factories](http://www.boost.org/doc/libs/1_52_0/libs/optional/doc/html/boost_optional/in_place_factories.html). I was going to look it up but then forgot. I'll update this post if I find out more. – Kerrek SB Jan 18 '13 at 18:20
  • @KerrekSB: The point of them is to use them with types that are not _copy constructible_... but I've never used them, I will see if I can figure them out – K-ballo Jan 18 '13 at 21:23
  • 1
    @KerrekSB: From the examples (well, tests), it seems that the usage would be `boost::optional v; v = boost::in_place(p2, p3, p4);` – K-ballo Jan 18 '13 at 21:27
  • Accepting this answer as it's taught me most, even if I'm going to leave the code the same! – Roddy Jan 18 '13 at 22:00
3

Looks perfectly reasonable to me just the way it is. It's clear, simple and relatively concise.

Lightness Races in Orbit
  • 358,771
  • 68
  • 593
  • 989
  • 1
    If only it didn't incur an unneeded allocation – K-ballo Jan 18 '13 at 19:08
  • Actually, the allocation is for the actual object itself. scoped_ptr sits on the stack. – thang Jan 19 '13 at 00:57
  • 1
    @thang: The `scoped_ptr` is an object too, and has _automatic storage duration_, which may or may not entail any "stack". It certainly has an allocation, though – Lightness Races in Orbit Jan 19 '13 at 02:41
  • what I meant is that when you do scoped_ptr a;, the scoped_ptr object is stored on the stack. if you do scoped_ptr a; and not do scoped_ptr a(new T), no allocation is performed. if you look above to the "correct" answer, no allocation actually happens because placement new is used on a block of memory on the stack. – thang Jan 19 '13 at 05:21
  • 1
    @thang: Allocation on the "stack" is still allocation – Lightness Races in Orbit Jan 19 '13 at 07:10
  • not sure why you call it the "stack" instead of stack. First, see here http://stackoverflow.com/questions/161053/c-which-is-faster-stack-allocation-or-heap-allocation and then see here http://www.wikihow.com/Use-Quotation-Marks-Correctly – thang Jan 19 '13 at 09:42
  • as you know, even with pooling, a heap allocation can be costly and can cause unnecessary fragmentation. In that sense when k-ballo and I were saying "allocation" (notice correct use of quotation), we were, by Gricean implicature, implying heap allocation. – thang Jan 19 '13 at 09:52
  • 1
    @thang: Ah, the old principle of "saying something different to what you mean". And why do I use quotations about "stack"? Because this question is tagged `c++`, a high-level computer programming language from which some implementation details are abstracted. First, see here http://stackoverflow.com/questions/4687310/memory-allocation-in-c and then stop being a smart-arse. – Lightness Races in Orbit Jan 19 '13 at 17:47
  • Actually, it's more of "meaning something that is different from the obviously wrong interpretation". Unfortunately, it is difficult to discuss efficiency or complexity (as used by OP) without bringing in implementation. If know an example of a compiler implementation of that specific use of automatic storage duration without using a stack, I would be interested in knowing. As it turns out, the "unneeded allocation" can in practical scenarios be a bottleneck, which, without implementation considerations, would not be obvious. In fact, it has more important implications than the obvious. – thang Jan 19 '13 at 21:37
1

I don't see the complexity... If you need to construct efficiently based on an if-condition declaring a pointer and using new is your only option. What you don't necessarily need to do is:

  1. Use a scoped_ptr (altough that's usually a good idea)
  2. Have the constructor in the if in your "main" code. Yours is a typical use case for a factory (see e.g. http://en.wikipedia.org/wiki/Factory_method_pattern).

EDIT: added "efficiently" in the second sentence.

Epikuros
  • 219
  • 2
  • 4
  • Good shout about the factory. – Lightness Races in Orbit Jan 18 '13 at 18:16
  • Well, without context the if-else may be enough and the factory an overkill... but good design seldom is! And this may be a good learning opportunity. – Epikuros Jan 18 '13 at 18:25
  • -1 "If you need to construct based on an if-condition declaring a pointer and using new is your only option" is incorrect. – Cheers and hth. - Alf Jan 18 '13 at 20:28
  • Right Alf. Added "efficiently" (meaning, construct once only) to make it correct. – Epikuros Jan 19 '13 at 13:18
  • @Epikorus: dynamic allocation is pretty inefficient, in general. so i'm afraid the edit adding the word "efficiently" only made things worse. – Cheers and hth. - Alf Jan 20 '13 at 19:39
  • @Alf: the question and the snippet in it don't in any way suggest that efficiency is a concern, so I went for the simplest (C++03) suggestion with the best design and efficiency for what we see. Using 'new', construction happens once only and there's the efficiency. Should efficiency be such a serious problem that dynamic allocation is a concern, well then I think, depending on the **context**, many other things can/should be done: e.g. removing the if at construction (maybe moving the choice at compile-time) or using a different memory allocator (like one with a preallocated pool), etc. – Epikuros Jan 20 '13 at 21:12
1
auto const doSomethingTo = []( Object&& o ) { o.doSomething(); };
doSomethingTo( condition? Object( p1 ) : Object( p1, p2, p3 ) );

Disclaimer: code not touched by compiler.


EDIT: the code above, when the Object( Object&& ) constructor is private, fails to compile with MSVC 11.0 (yes even last year's November CTP), but does compile fine with MinGW g++ 4.7.1 and with some version of clang.

It appears that it should compile.

So, it's probably a bug in Visual C++ – but unfortunately I didn't find an easy workaround.


An uneasy workaround for the assumed-to-be Visual C++ compiler bug:

#include <fstream>
#include <iostream>
using namespace std;

class Object
{
private:
    Object( Object const& );
    Object( Object&& );
public:
    void doSomething() const {}
    Object( int ) {}
    Object( int, int, int ) {}
};

int main( int argc, char* argv[] )
{
    int p1 = 0, p2 = 0, p3 = 0;
    bool condition = argc == 2;

    auto const doSomething1 = [=]() { Object o( p1 ); o.doSomething(); };
    auto const doSomething2 = [=]() { Object o( p1, p2, p3 ); o.doSomething(); };

    if( condition ) { doSomething1(); } else { doSomething2(); }
}

Another answer maintains that new (read: a dynamic allocation) is your only option.

That's wrong.

Community
  • 1
  • 1
Cheers and hth. - Alf
  • 135,616
  • 15
  • 192
  • 304
  • Most of the compilers used in actual production will not accept your solution. You need C++11 (still not yet widespread) for rvalue references and lambda functions. But the basic idea is probably valid: move all of the code using the object into a function taking the object by reference, and then have two different variables in the function which calls it. – James Kanze Jan 19 '13 at 01:27
  • @JamesKanze: thanks, i suspected as much… and yes, the basic idea is also applicable in C++03, where it corresponds to either the workaround shown last, or the application of a function that yields a reference to a temporary in order to use the more straightforward original construction. i do not understand why the answer where you have to choose between an unreasonable requirement (default-constructable) + possible side effect, or inefficiency of dynamic allocation + dependency on big library, has been selected as “the solution” – it’s about the worst possible answer IMHO – but such is SO. – Cheers and hth. - Alf Jan 19 '13 at 10:25
  • I certainly agree with you about the worst possible answer being the one selected as "the solution". But as you say, such is SO. – James Kanze Jan 20 '13 at 16:02
  • @JamesKanze, well it's my choice ;-) But (as I commented when I accepted it) I felt it the 'best' answer because I learnt most from it: use of boost::in_place with boost::optional. The "terrible hack" is exactly that, and I wouldn't consider it. C++11 isn't an option, so this answer isn't - yet- *useful* for me, and the proliferation of little 'dosomething' functions in C++03 is not what I'd consider a 'clean' approach. – Roddy Jan 21 '13 at 17:42
1

There's really nothing wrong with your solution, although as others have already mentionned, it would be more readably if you used the conditional operator rather than if's. But you should consider the possibility of refactoring. If you factor out all of the code which uses the object into a separate function (taking the object by reference), then something like:

if ( condition ) {
    Object a( p1 );
    doWhatever( a );
} else {
    Object a( p2, p3, p4 );
    doWhatever( a );
}

might be preferable (or not—I don't think that there's any "right" answer with regards to choosing between these two).

James Kanze
  • 142,482
  • 15
  • 169
  • 310
0

I think your code is OK.

You just may want to consider the conditional operator ? :

boost::scoped_ptr<Object> a( condition ? new Object(p1) : new Object(p2,p3,p4) );
a->doSomething();
Lightness Races in Orbit
  • 358,771
  • 68
  • 593
  • 989
Mr.C64
  • 37,988
  • 11
  • 76
  • 141
  • 2
    @LightnessRacesinOrbit: Well done for the stupid comment. I think readability (or lack of) is in the eye of the beholder. – Mr.C64 Jan 18 '13 at 18:33
  • 1
    Why turn to insults? Please don't do that. – Lightness Races in Orbit Jan 18 '13 at 18:34
  • really, who are those people upvoting comments about who's best at insulting or who's done it. pretty silly. anyway, re the answer, a dynamic allocation is a pretty high cost in C++, not to be incurred lightly, and especially not when easy alternatives exist, so i wouldn't do this – Cheers and hth. - Alf Jan 18 '13 at 20:47
  • The conditional operator certainly improves the readability here, but it doesn't address his specific problem, since it also requires support for copy. – James Kanze Jan 19 '13 at 01:28
0

So, here is a quick trick to make this work, without manually constructing objects. Instead, I created a Deferred<T> template that represents an object in automatic storage whose construction is deferred (and possibly never occurs).

The buff in Deferred should be replaced with a union, because that will handle alignment issues (assuming you have C++11 features to support that). Asserts that constructed is true when you call get() is probably a good idea.

Once you have constructed your object, you can implicitly cast your Deferred<T> to a T&, then use that T& as an alias to the deferred-constructed T.

In theory, you could do away with the constructed bool if you could prove that it would always be constructed, but I'd advise against it. Other than that, this should be nearly as efficient as you can pull it off. And with the C++11 union case, it might even be standards compliant.

Oh yes, and it should be enhanced with perfect forwarding.

#include <utility>

// does not handle alignment issues:
template<typename T>
struct Deferred {
   Deferred():constructed(false) {}
  operator T&() { return get(); }
  T& get() { return *reinterpret_cast<T*>(&buff[0]); }
  template<typename... Args>
  T& construct( Args... args ) {
    new(&buff[0]) T(args...);
    constructed = true;
    return get();
  }
  ~Deferred() {
    if (constructed) {
      get().~T();
    }
  }
private:
  bool constructed;
  char buff[sizeof(T)];
};

#include <iostream>

struct Object {
  bool is_int;
  Object( int x ):is_int(true) {}
  Object( double d ):is_int(false) {}
  ~Object() {
    std::cout << "~Object("<<is_int<<") destroyed\n";
  }
};

enum which_test {
  as_int,
  as_double,
  do_not,
};
void test(which_test v) {
  std::cout << v << "\n";
  Deferred<Object> o;
  if(v==as_int) {
    o.construct( 7 );
  } else if (v==as_double) {
    o.construct( 7.0 );
  } else {
  }
}

int main() {
  test(as_int);
  test(as_double);
  test(do_not);
}
Yakk - Adam Nevraumont
  • 235,777
  • 25
  • 285
  • 465