问题描述:

Consider the following code snippet.

template <T>

MyPtr<T> CreateObject()

{

// Do something here first...

// return our new object

return MyPtr<T>(new T());

}

class Foo

{

private:

Foo() { }

public:

static MyPtr<Foo> GetNewInstance()

{

// ERROR: Foo is private...

return CreateObject<Foo>();

}

};

class Bar

{

public:

Bar() { }

};

int main()

{

MyPtr<Bar> bar = CreateObject<Bar>();

return 0;

}

Without resorting to macro for CreateObject (I like the syntax of MyPtr<type> obj = CreateObject<type>(params)), is there a way to make the function CreateObject share the same context as the caller function, thus able to access private Foo c'tor? 'friend' is not what I'm looking for as it would mean anyone calling CreateObject would have access to private Foo c'tor, which is not what I want. Overloading the new operator wouldn't work either as it is imperative that a MyPtr is returned instead of just T* (by assigning T* to MyPtr assigns a type to the object that is required somewhere else).

I guess what I'm looking for is something in between a macro and a template function (syntax of a template function but gets expanded fully like a macro). It would be quite useful to have this feature in this particular case.

网友答案:

This is basically the same as attempting to use make_shared with a private constructor.

The only way to allow this is with friend. You're pretty much stuck in this case I'm afraid.

网友答案:

Well, you could do that with the passkey pattern:

template<class T, class PassKey>
MyPtr<T> CreateObject(PassKey const& key)
{
  return new T(key);
}

class FooKey{
private:
  FooKey(){} // private ctor
  FooKey(const FooKey&); // undefined private copy ctor

  friend class Foo;
};

class Foo{
public:
  // public ctor
  Foo(FooKey const&){}

  static MyPtr<Foo> GetNewInstance() 
  {
    return CreateObject<Foo>(FooKey());
  }
};

Example at Ideone.

With C++0x, this can be done much easier than creating a new Key struct every time, since template parameters are now allowed to be friends:

template<class T>
struct PassKey{
private:
  PassKey(){}
  PassKey(const PassKey<T>&);

  friend T;
};
网友答案:

I am not sure as to what you are trying to achieve. The simplification to post the problem here has taken away the actual need for the whole thing. So I will just assume that you know what you are doing, and that you really need this (and I suggest that you rethink whether you do need it, as I don't see a point...)

At any rate, you can solve the problem by passing a creator callback to the CreateObject template:

template <typename T, typename Creator>
MyPtr<T> CreateObject( Creator creator )
{
    // Do something here first...
    return MyPtr<T>(creator());
}
class Foo
{
private:
    Foo() {}
    static Foo* create() { return new Foo(); }
public:
    static MyPtr<Foo> GetNewInstance() {
        return CreateObject<Foo>( &Foo:create );
    }
// ...
};

The actual issue though, is what does Do something here first actually does that forces you into this complex creation patterns. The fact that it has to be executed before the creation of the new object seems to indicate that there are hidden dependencies not shown in the code, and that usually end up in maintenance nightmares, where someone down the line reorders some code, or adds a new constructor and everything seems to fall apart. Revisit your design and consider whether those dependencies can be simplified or made explicit.

网友答案:

Since you are newing up the object in the very end it really doesn't relate to your CreateObject function. So Change the function prototype to:

template <typename T>
MyPtr<T> CreateObject(T* const p)
{
  //...
  return MyPtr<T>(p);
}

Usage:

static MyPtr<Foo> GetNewInstance() 
{
  return CreateObject(new Foo());
}
网友答案:

is there a way to make the function CreateObject share the same context as the caller function

Yes, pass the context you need as an argument (either as an argument to the template, or as an argument to the function).

In practice, move the new T call to a separate function (or struct template, as I chose to do here), like this:

// Dummy representation of your pointer type
template <typename T>
struct MyPtr
{
    MyPtr( T *p ) { }
};

// Default constructor template; may be specialized to not use "new" or so.
template <typename T>
struct Constructor
{
    static T *invoke() { return new T; }
};

// Needs to be a struct (or class) so 'C' can have a default value
template <typename T, typename C = Constructor<T> >
struct CreateObject
{
    MyPtr<T> operator()() {
        return MyPtr<T>( C::invoke() );
    }
};

class Foo
{
private:
    friend struct Constructor<Foo>;
    Foo() { }

public:
    static MyPtr<Foo> GetNewInstance() 
    {
        return CreateObject<Foo>()();
    }
};

If you want to handle different constructor signatures (read: if not all types T have the same constructor signature), you could also choose to not pass the Constructor as a template to the CreateObject struct, but instead use a function argument. That way, you could 'load' a Constructor like this:

// ...
static MyPtr<Foo> GetNewInstance() 
{
     Constructor<Foo> c( arg1, arg2, arg3 );
     return CreateObject<Foo>( c );
}
相关阅读:
Top