我有一个类messenger
,它依赖于printer
个实例。 printer
是一个多态基类,实际对象传递给构造函数中的messenger
。
对于非多态对象,我只会执行以下操作:
class messenger {
public:
messenger(printer const& pp) : pp(pp) { }
void signal(std::string const& msg) {
pp.write(msg);
}
private:
printer pp;
};
但是当printer
是多态基类时,它不再起作用(切片)。
考虑到
,这项工作的最佳方法是什么?printer
类不需要虚拟clone
方法(=需要依赖复制构造)。我不想传递指向构造函数的指针,因为API的其余部分正在使用真实对象,而不是指针,并且在这里将指针作为参数会令人困惑/不一致。
在C ++ 0x下,我或许可以使用unique_ptr
和模板构造函数:
struct printer {
virtual void write(std::string const&) const = 0;
virtual ~printer() { } // Not actually necessary …
};
struct console_printer : public printer {
void write(std::string const& msg) const {
std::cout << msg << std::endl;
}
};
class messenger {
public:
template <typename TPrinter>
messenger(TPrinter const& pp) : pp(new TPrinter(pp)) { }
void signal(std::string const& msg) {
pp->write(msg);
}
private:
std::unique_ptr<printer> pp;
};
int main() {
messenger m((console_printer())); // Extra parens to prevent MVP.
m.signal("Hello");
}
这是最好的选择吗?如果是这样,那么在0x之前最好的方法是什么?有没有办法摆脱构造函数中完全不必要的副本?不幸的是,移动临时在这里不起作用(对吧?)。
答案 0 :(得分:9)
没有虚拟克隆方法,无法克隆多态对象。所以你可以:
最后一个是您对C ++ 0x std::unique_ptr
的建议,但在这种情况下,C ++ 03 std::auto_ptr
会为您提供完全相同的服务(即您不需要移动它而且它们是相同的。)
编辑:好的,嗯,还有一个方法:
printer
本身成为实际实现的智能指针。比起它的可复制性和多态性同时代价是一些复杂性。答案 1 :(得分:2)
将评论扩展为正确答案......
这里主要关注的是所有权。从您的代码中可以看出,messenger
的每个实例都拥有自己的打印机实例 - 但实际上您传入的是预构建的打印机(可能是一些额外的状态),然后您需要将其复制到您的自己的printer
实例。鉴于对象printer
的隐含性质(即打印某些东西),我认为打印的东西是共享资源 - 从这个角度看,每个{{1}都没有意义。实例拥有它自己的messenger
副本(例如,如果您需要锁定以访问printer
,该怎么办?)
从设计的角度来看,std::cout
构建需要的实际上是指向某些共享资源的指针 - 从这个角度来看,messenger
(更好的是,shared_ptr
)是一个更好的选择。
现在,如果您不想使用weak_ptr
,并且您希望存储引用,请考虑是否可以将weak_ptr
与messenger
类型耦合,即耦合留给用户,你不在乎 - 当然这主要的缺点是printer
不可包含。注意:您可以指定可以键入messenger
的特征(或策略)类,这样可以提供打印机的类型信息(并且可以由用户控制)。
第三种选择是如果您完全控制打印机组,在这种情况下保持变体类型 - 它更清晰恕我直言并避免多态性。
最后,如果你不能联合,你无法控制打印机,并且你想要自己的messenger
实例(相同类型),转换构造函数模板是前进的方法,但是添加{{1防止被错误地调用(即正常复制ctor)。
总而言之,我会将打印机视为共享资源并保留printer
,坦白说它可以更好地控制共享资源。
答案 2 :(得分:2)
Unfortunately, moving the temporary doesn’t work here (right?).
错误。是,呃,直言不讳。这就是rvalue引用的内容。一个简单的过载可以迅速解决手头的问题。
class messenger {
public:
template <typename TPrinter>
messenger(TPrinter const& pp) : pp(new TPrinter(pp)) { }
template <typename TPrinter>
messenger(TPrinter&& pp) : pp(new TPrinter(std::move(pp))) { }
void signal(std::string const& msg) {
pp->write(msg);
}
private:
std::unique_ptr<printer> pp;
};
相同的概念将适用于C ++ 03,但交换unique_ptr
用于auto_ptr
并抛弃右值引用过载。
另外,你可以考虑使用C ++ 03的某种“虚拟”构造函数,如果你对一个有点狡猾的界面没问题的话。
class messenger {
public:
template <typename TPrinter>
messenger(TPrinter const& pp) : pp(new TPrinter(pp)) { }
template<typename TPrinter> messenger(const TPrinter& ref, int dummy)
: pp(new TPrinter())
{
}
void signal(std::string const& msg) {
pp->write(msg);
}
private:
std::unique_ptr<printer> pp;
};
或者您可以考虑auto_ptr
在C ++ 03中用于“移动”的相同策略。谨慎使用,当然,但完全合法和可行。问题在于你正在影响所有printer
子类。
答案 3 :(得分:1)
为什么不想传递指针或智能指针?
无论如何,如果你总是在构造函数中初始化打印机成员,你可以使用引用成员。
private:
printer& pp;
};
并在构造函数初始化列表中初始化。
答案 4 :(得分:1)
当你有一把金锤时,一切看起来像指甲
好吧,我最新的金锤是类型擦除。说真的,我不会使用它,但是再一次,我会传递一个指针,让调用者创建并注入依赖。
struct printer_iface {
virtual void print( text const & ) = 0;
};
class printer_erasure {
std::shared_ptr<printer_iface> printer;
public:
template <typename PrinterT>
printer_erasure( PrinterT p ) : printer( new PrinterT(p) ) {}
void print( text const & t ) {
printer->print( t );
}
};
class messenger {
printer_erasure printer;
public:
messenger( printer_erasure p ) : printer(p) {}
...
};
好吧,可以说这个和模板提供的解决方案是完全相同的,只有轻微的区别是类型擦除的复杂性移动在类之外。 messenger
类有自己的职责,类型擦除不是其中之一,可以委派。
答案 5 :(得分:0)
如何约束class messanger
?
template <typename TPrinter>
class messenger {
public:
messenger(TPrinter const& obj) : pp(obj) { }
static void signal(printer &pp, std::string const& msg) //<-- static
{
pp->write(msg);
}
private:
TPrinter pp; // data type should be template
};
请注意,signal()
是static
。这是为了利用virtual
的{{1}}能力,并避免生成class printer
的新副本。你唯一需要做的就是调用函数,
signal()
假设您有其他数据类型,然后signal(this->pp, "abc");
与模板类型无关,那么这些数据类型可以移动到非模板基类,并且pp
可以继承该基类。我没有详细描述,但我希望这一点应该更清楚。