多态类成员变量

时间:2011-05-23 12:05:15

标签: c++ oop c++11

我有一个类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是多态基类时,它不再起作用(切片)。

考虑到

,这项工作的最佳方法是什么?
  1. 我不想传递指向构造函数的指针,
  2. printer类不需要虚拟clone方法(=需要依赖复制构造)。
  3. 我不想传递指向构造函数的指针,因为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之前最好的方法是什么?有没有办法摆脱构造函数中完全不必要的副本?不幸的是,移动临时在这里不起作用(对吧?)。

6 个答案:

答案 0 :(得分:9)

没有虚拟克隆方法,无法克隆多态对象。所以你可以:

  • 传递并保留引用并确保在代码构造信使中的信使之前没有销毁打印机,
  • 传递并按住智能指针并使用新的
  • 创建打印机实例
  • 使用clone方法或
  • 传递引用并在堆上创建打印机实例
  • 将对实际类型的引用传递给模板,并在您仍然知道类型时使用new创建实例。

最后一个是您对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_ptrmessenger类型耦合,即耦合留给用户,你不在乎 - 当然这主要的缺点是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可以继承该基类。我没有详细描述,但我希望这一点应该更清楚。