英文:
How to write templated factory function for both std::shared_ptr and std::unique_ptr
问题
以下是翻译好的部分:
我经常编写类似以下签名的工厂:
std::unique_ptr<AbstractType> createUnique(IDType id);
std::shared_ptr<AbstractType> createShared(IDType id);
前者会执行类似以下操作:
switch (id)
{
case kID1:
return std::make_unique<Type1>();
}
而后者会执行以下操作:
switch (id)
{
case kID1:
return std::make_shared<Type1>();
}
唯一不同的是这两个函数的“make”函数(make_shared
或make_unique
)和返回类型(shared_ptr
或unique_ptr
)。这导致了代码重复。
我想知道如何编写一个模板化的create
函数,它接受指针类型和“make”函数类型,并使用它们。类似于以下内容:
template <typename PTR, typename MAKE>
PTR create(IDType id)
{
switch (id)
{
case kID1:
return MAKE<Type1>();
}
}
我知道上面的代码是无效的。
此外,我知道可以从std::unique_ptr
创建std::shared_ptr
,反之亦然,但是这个工厂将在不同的应用程序中使用,其中一个可能使用shared_ptr
,而另一个可能使用unique_ptr
。这样,代码是可重复使用的,但也对特定用例高效。
英文:
I often write factory which have a signature similar to the following:
std::unique_ptr<AbstractType> createUnique(IDType id);
std::shared_ptr<AbstractType> createShared(IDType id);
Where the former would do something like:
switch (id)
{
case kID1:
return std::make_unique<Type1>();
}
And the latter
switch (id)
{
case kID1:
return std::make_shared<Type1>();
}
Well, the only different between the two functions is the "make" function used (make_shared
or make_unique
) and the return type (shared_ptr
or unique_ptr
). This leads to code duplication.
I am wondering how I could write a templated create
function which accepts the pointer type and "make" function type and uses those instead. Something like:
template <typename PTR, typename MAKE>
PTR create(IDType id)
{
switch (id)
{
case kID1:
return MAKE<Type1>();
}
}
I am aware that the above is not valid code.
Also, I am aware that a std::shared_ptr
could be created from an std::unique_ptr
and visa versa, but I this factory would be used in different applications where one might use the shared_ptr
and the other might use the unique_ptr
. This way, the code is re-usable but also effecienct for the particular use-case.
答案1
得分: 6
只有一个返回类型是 std::unique_ptr<AbstractType>
。 如果这样做,你可以使用std::unique_ptr
和std::shared_ptr
来调用该函数,比如:
auto my_ptr = createInterface(id);
这样,my_ptr
将是一个 unique_ptr
或者
std::shared_ptr<AbstractType> my_ptr = createInterface(id);
现在,返回的 unique_ptr
被转换为 shared_ptr
。
英文:
My advice, only have a single return type of std::unique_ptr<AbstractType>
. If you do that then you can use the function with std::unique_ptr
and std::shared_ptr
like
auto my_ptr = createInterface(id);
which has my_ptr
being a unique_ptr
or
std::shared_ptr<AbstractType> my_ptr = createInterface(id);
and now the returned unique_ptr
is converted into a shared_ptr
.
答案2
得分: 4
To avoid loosing use of std::make_shared
which optimizes use of memory, I would approach this issue this way:
避免失去对 `std::make_shared` 的使用,该方法优化了内存使用,我会以以下方式解决这个问题:
英文:
To avoid loosing use of std::make_shared
which optimizes use of memory, I would approach this issue this way:
class AbstractType {
public:
virtual ~AbstractType() = default;
virtual int f() const = 0;
};
class Foo : public AbstractType {
public:
int f() const override
{
return 1;
}
};
class Bar : public AbstractType {
public:
int f() const override
{
return 2;
}
};
enum class IDType {
Foo,
Bar,
};
class Factory {
public:
std::unique_ptr<AbstractType> createUnique(IDType id);
std::shared_ptr<AbstractType> createShared(IDType id);
private:
template <typename MakePtr>
auto createUniversal(IDType id);
};
//======================
class WrapMakeUnique {
public:
template <typename Base, typename T, typename... Args>
static auto make(Args... args) -> std::unique_ptr<Base>
{
return std::make_unique<T>(std::forward<Args>(args)...);
}
};
class WrapMakeShared {
public:
template <typename Base, typename T, typename... Args>
static auto make(Args... args) -> std::shared_ptr<Base>
{
return std::make_shared<T>(std::forward<Args>(args)...);
}
};
template <typename MakePtr>
auto Factory::createUniversal(IDType id)
{
switch (id) {
case IDType::Foo:
return MakePtr::template make<AbstractType, Foo>();
case IDType::Bar:
return MakePtr::template make<AbstractType, Bar>();
}
throw std::invalid_argument { "IDType out of range" };
}
std::unique_ptr<AbstractType> Factory::createUnique(IDType id)
{
return createUniversal<WrapMakeUnique>(id);
}
std::shared_ptr<AbstractType> Factory::createShared(IDType id)
{
return createUniversal<WrapMakeShared>(id);
}
答案3
得分: 1
你的通用工厂方法非常接近:
template <typename PTR, typename MAKE>
PTR create(IDType id)
{
switch (id)
{
case kID1:
return MAKE<Type1>();
}
}
你所错过的关键部分似乎是 (1) MAKE
是一个类型,所以 MAKE<Type1>()
是我们可能需要跳过的语法,以及 (B) 如何使用它。
理想的使用方式是使用两个工厂类:
template<class Base>
struct make_unique_factory {
using PTR = std::unique_ptr<Base>;
template<class Derived, class... Args>
PTR create(Args&&... args) const
{return std::make_unique<Derived>(std::forward<Args>(args)...);}
};
template<class Base>
struct make_shared_factory {
using PTR = std::shared_ptr<Base>;
template<class Derived, class... Args>
PTR create(Args&&... args) const
{return std::make_shared<Derived>(std::forward<Args>(args)...);}
};
(我们使用类而不是方法,以便编译器更容易内联。方法参数更难内联)
然后使用方式与你之前的类似:
template <typename MAKE>
auto create(IDType id) -> typename MAKE::PTR
{
switch (id)
{
case kID1:
return MAKE{}.template create<Type1>();
}
}
std::unique_ptr<AbstractType> createUnique(IDType id) {
return create<make_unique_factory<AbstractType>>(id);
}
std::shared_ptr<AbstractType> createShared(IDType id) {
return create<make_shared_factory<AbstractType>>(id);
}
这是你遇到问题的原因可能是这行代码:
MAKE{}.template create<Type1>()
所以我来解释一下。MAKE{}
构造了 MAKE
类的一个实例。然后,我们在该实例上调用 create<Type1>()
方法。但是,在编译器编译这个 create()
方法时,它还不知道 MAKE
是什么,所以不知道它的 create()
方法是一个模板方法。因此,令人讨厌的是,我们必须明确告诉编译器这个方法是一个模板方法,因此出现了 MAKE{}.template create<Type1>()
。
英文:
Your generic factory method is very close:
template <typename PTR, typename MAKE>
PTR create(IDType id)
{
switch (id)
{
case kID1:
return MAKE<Type1>();
}
}
The key parts you're missing appear to be that (1) MAKE
is a type, so MAKE<Type1>()
is a syntax we'll probably want to skip, and (B) how to use it.
The ideal way to use it is with two factory classes:
template<class Base>
struct make_unique_factory {
using PTR = std::unique_ptr<Base>;
template<class Derived, class... Args>
PTR create(Args&&... args) const
{return std::make_unique<Derived>(std::forward<Args>(args)...);}
};
template<class Base>
struct make_shared_factory {
using PTR = std::shared_ptr<Base>;
template<class Derived, class... Args>
PTR create(Args&&... args) const
{return std::make_shared<Derived>(std::forward<Args>(args)...);}
};
(We use classes and not methods so that the compiler has an easier time inlining. Method parameters are harder to inline)
And then use is similar to what you had:
template <typename MAKE>
auto create(IDType id) -> typename MAKE::PTR
{
switch (id)
{
case kID1:
return MAKE{}.template create<Type1>();
}
}
std::unique_ptr<AbstractType> createUnique(IDType id) {
return create<make_unique_factory<AbstractType>>(id);
}
std::shared_ptr<AbstractType> createShared(IDType id) {
return create<make_shared_factory<AbstractType>>(id);
}
http://coliru.stacked-crooked.com/a/bc3d5d2431a0b0da
The reason you had trouble is probably this line:
MAKE{}.template create<Type1>()
So I'll break that down. MAKE{}
constructs an instance of the MAKE
class. Then, we call the create<Type1>()
method on that instance. However, when the compiler is compiling this create()
method, it doesn't yet know what MAKE
is, so doesn't know that its create()
method is a template method. So annoyingly, we have to tell it explicitly that this method is a template method, leading to MAKE{}.template create<Type1>()
.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论