【问题标题】:Does C++11's decltype make clone unnecessary?C++11 的 decltype 是否使克隆变得不必要?
【发布时间】:2012-05-12 13:15:27
【问题描述】:

clone 范式用于制作派生类的副本,而无需向下转换为基类类型。不幸的是,clone 必须在每个子类中实现(或使用带有 CRTP 的 mixin)。

C++11 的decltype 有没有可能让这变得不必要?

我不认为下面的代码实际上复制了original,而只是指向它的引用。当我尝试使用new decltype(*original) 时,出现错误: error: new cannot be applied to a reference type.

clone 仍然是 C++11 的路吗?或者有什么新方法可以使用 RTTI 从基类指针复制派生类对象?

#include <iostream>

struct Base
{
  virtual void print()
  {
    std::cout << "Base" << std::endl;
  }
};

struct Derived : public Base
{
  int val;
  Derived() {val=0;}
  Derived(int val_param): val(val_param) {}
  virtual void print()
  {
    std::cout << "Derived " << val << std::endl;
  }
};

int main() {
  Base * original = new Derived(1);
  original->print();

  // copies by casting down to Base: you need to know the type of *original
  Base * unworking_copy = new Base(*original);
  unworking_copy->print();

  decltype(*original) on_stack = *original;
  on_stack.print();
  return 0;
}

【问题讨论】:

    标签: c++ c++11 clone


    【解决方案1】:

    decltype 是一个静态结构。像所有 C++ 类型构造一样,它不能推断出对象的 runtime 类型。 decltype(*original) 就是 Base&amp;。

    【讨论】:

    • 这个答案(连同我之前的答案版本)是危险的错误。 decltype(*original) 实际上是Base&amp;。
    • @Mankarse 是的,这就是onstack.print() 使用Derived::print 的原因。但是@NicolBolas 的观点是正确的:decltype 不能给我运行时类型信息。
    • 你可以使用 std::decay 将 Base& 变成 Base。
    • @AndrewTomazos-Fathomling +1 很高兴知道。
    • @AndrewTomazos-Fathomling:有趣,为什么要在这里使用std::decay 而不是std::remove_reference?
    【解决方案2】:

    decltype(顾名思义)给出了它所应用的表达式的声明的类型(静态类型)。

    decltype(*original) 是 Base&amp;,所以你的代码会打印出来

    Derived 1
    Base
    Derived 1
    

    但第三种情况不会复制。

    克隆(或该模式的某些变体)仍然是 C++11 的方式。

    【讨论】:

    • 你可以使用 std::decay 将 Base& 变成 Base。
    【解决方案3】:

    decltype 不能也不会恢复动态对象类型。它是一个纯粹的静态构造。

    复制对象没有神奇的方法。您必须调用其确切的最终动态类型的构造函数somewhere。

    【讨论】:

    • 我喜欢你最后的两句话! :)
    【解决方案4】:

    克隆仍然是 C++11 的方式吗?或者有什么新方法可以使用 RTTI 从基类指针复制派生类对象?

    如果有人对非侵入式克隆感兴趣,C++11 的 lambdas 似乎提供了一些新的克隆工具:考虑到克隆问题,我承认制造对象原始实例的人也应该是可以帮助构建副本的人。考虑一下您的所有对象都由某些Factory 制造的情况。在这种情况下,除了通常的create 接口外,您的工厂还可以配备clone 接口,例如像这样:

    #include <iostream>
    #include <functional>
    #include <memory>
    #include <unordered_map>
    
    template <typename BASE>
    struct
    Factory {
        private: using
        TCloneFn = std::function<std::shared_ptr<BASE>(BASE const * const)>;
    
        private:
        static std::unordered_map<BASE const*,TCloneFn> cloneFnMap;
    
        public: template <typename DERIVED_TYPE, typename...TS>
        static std::shared_ptr<BASE>
        create(TS...args) {
            BASE* obj = new DERIVED_TYPE(args...);
            const std::shared_ptr<BASE> pNewObj =
                std::shared_ptr<BASE>(
                    obj,
                    [&](BASE* p){
                        cloneFnMap.erase(p);
                        delete p;
                    }
                );
    
            cloneFnMap[obj] = [&](BASE const * const orig){
                std::shared_ptr<BASE> pClone = create<DERIVED_TYPE>(std::ref(static_cast<DERIVED_TYPE const &>(*orig)));
                return pClone;
            };
            return pNewObj;
        }
    
        public: static std::shared_ptr<BASE>
        clone(std::shared_ptr<BASE const> original) {
            return cloneFnMap[original.get()](original.get());
        }
    };
    
    template <typename BASE> std::unordered_map<BASE const*,typename Factory<BASE>::TCloneFn> Factory<BASE>::cloneFnMap;
    
    class Base {
        public: virtual ~Base() throw() {}
        public: virtual void whoAmI() const {
            std::cout << "I am Base instance " << this << "\n";
        }
    };
    
    
    class Derived : public Base {
        std::string name;
        public: Derived(std::string name) : name(name) {}
        public: Derived(const Derived&other) : name("copy of "+other.name) {
        }
        private: virtual void whoAmI() const {
            std::cout << "I am Derived instance " << this << " " << name << "\n";
        }
    };
    
    int main() {
        std::shared_ptr<Base> a = Factory<Base>::create<Derived>("Original");
        a->whoAmI();
        std::shared_ptr<Base> copy_of_a = Factory<Base>::clone(a);
        copy_of_a->whoAmI();
        std::shared_ptr<Base> copy_of_a_copy = Factory<Base>::clone(copy_of_a);
        copy_of_a_copy->whoAmI();
        return 0;
    }
    

    诀窍是记住原始文件是如何在 Factory::create 方法中构造的(通过将指向对象的指针与将调用复制构造函数的 lambda 关联)。永远不要扔掉旧的蓝图,你以后可能会用到它们;-)

    坦率地说,我仍然更喜欢带有 CRTP 等的旧 clone 解决方案,但这可能会引发一些新想法或派上用场。

    上面的代码也在http://ideone.com/kIPFt2

    编辑:正如 wjl 评论的那样,代码的第一个版本将导致 cloneFnMap 稳步增长。经过深思熟虑,我决定通过在返回的shared_ptrs 中引入一个自定义删除器来修复代码,这样cloneFnMap 也将得到清理。不过,这应该只是一个实验性的概念验证代码。

    【讨论】:

    • 如果有人要实际上使用此技术,请确保使用自定义删除器创建 shared_ptr,该删除器会在对象被销毁时从 cloneFnMap 中删除指针,否则它会永远成长。
    • 当然,我只是尽量减少事情。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-04
    • 2017-09-12
    • 2011-06-18
    • 1970-01-01
    相关资源
    最近更新 更多