【问题标题】:How do I define an any-to-any mapping in c++?如何在 C++ 中定义任意映射?
【发布时间】:2012-09-01 06:52:11
【问题描述】:

我想在 c++ 中定义一个完全通用的映射,我可以将任何东西映射到任何东西。

我尝试了 std::map 但 K 和 V 应该是什么才能使其足够通用,以便我可以将基元或对象(作为键)映射到其他基元或对象(作为值)。

或者我可以使用其他机制吗?

编辑:为澄清起见,我试图在基类中定义一个关系(我的所有类都是从它派生的),这将允许我将任意数据附加到我的类。最简单的方法是一个名称-值对,其中上面的键是一个字符串。我想知道我是否可以做一些更通用的事情?

【问题讨论】:

    标签: c++ map


    【解决方案1】:

    不可能——因为它应该是。这样的映射将毫无价值,因为您不能依赖键或值的任何有意义的行为,并且不可能设计对“任何事物”有意义的二进制关系或哈希函数,或者可以对任何事物进行操作类型,所以它甚至远不及可能的领域。

    编辑:没有什么可以阻止 std::unordered_map<std::string, boost::any>- 或者实际上,boost::any 恰好包含某些类型的 std::unordered_map

    但是,您的设计似乎非常有问题。你基本上完全颠覆了编译器,没有明显的好处。为什么你会从一个共同的基础派生每个类?你到底为什么要附加任意数据?将数据放入类中的常用方法是将其放入类中,而不是试图强迫 C++ 成为一种解释性语言,从而破坏你所有的安全、性能和理智。

    【讨论】:

    • 顺便说一句,std::unordered_map 的解决方案很好。我之前 +1 的更多理由!
    • 好点@DeadMG。我从一个公共类派生,因为这是有限的功能(所以没有多少派生类),但我事先不知道这些类的客户需要附加哪些作为补充数据。
    • @AndrewS.:让客户担心将数据与他们关联——如果他们甚至需要该功能。他们可以做到std::unordered_map<Derived*, ThatDataWhichINeed> 就好了。
    【解决方案2】:

    这是可能的——所以在这一点上我不同意@DeadMG。

    毫无价值——在这一点上完全同意,

    但是我不理解回答的概念,我的意思是“不要这样做”,而是回答“可以以这种方式完成,但我的建议是不要这样做”。我不假装自己是“生活老师”——我只是在回答,

    对于值 - 使用类似 boost::any 的东西。

    对于键 - 它更复杂 - 因为 std::map 定义了键中的顺序。所以通用键必须遵循这些规则:

    1. 如果真实键类型相同 - 使用真实键的顺序
    2. 如果真正的键不相同 - 您必须定义类型之间的顺序(如 typeinfo::name() 的顺序)
    3. 通用密钥必须是可复制构造的

    让我们看看我对密钥的建议(使用类型擦除):

    template <typename T>
    struct GenKeyTypeOrder;
    
    
    class GenKeyImplInt {
    public:
       // true if before other Key in other 
       virtual bool before(const GenKeyImplInt&) const = 0;
       // type value
       virtual int typeOrder() const = 0;
    
       virtual GenKeyImplInt* clone() const = 0;
       virtual ~GenKeyImplInt() {}
    };
    
    template <typename RealKey>
    class GenKeyImpl : public GenKeyImplInt {
    public:
       GenKeyImpl(RealKey realKey) : realKey(realKey) {}
       // true if before other Key in other 
       virtual bool before(const GenKeyImplInt& r) const 
       { 
          const GenKeyImpl* rp = dynamic_cast<const GenKeyImpl*>(&r);
          if (rp) return realKey < rp->realKey;
          return typeOrder() < r.typeOrder();
       }
       // type value
       virtual int typeOrder() const { return GenKeyTypeOrder<RealKey>::VALUE; }
    
       virtual GenKeyImpl* clone() const { return new GenKeyImpl(*this); }
    private:
       RealKey realKey;
    };
    
    class GenKey {
    public:
       // true if before other Key in other 
       friend bool operator < (const GenKey& l, const GenKey& r) 
       {
           return l.impl->before(*r.impl);
       }
       template <typename T>
       GenKey(T t) : impl(new GenKeyImpl<T>(t)) {}
       GenKey(const GenKey& oth) : impl(oth.impl->clone()) {}
       ~GenKey() { delete impl; }
    private:
       GenKey& operator = (const GenKey& oth); // not defined
       GenKeyImplInt* impl;
    };
    
    
    // define for every type you want be used as generic key
    template <>
    struct GenKeyTypeOrder<int> { enum { VALUE = 0 }; };
    template <>
    struct GenKeyTypeOrder<std::string> { enum { VALUE = 1 }; };
    

    ideone 的完整示例 另见article

    【讨论】:

    • 你还没有将任何东西映射到任何东西。您只有已专门注册的映射类型并且支持operator&lt;
    • @DeadMG 这只是解决问题的第一次尝试。我可以很容易地想象通用类型的顺序——这样就不需要专门注册该类型。例如。使用 typeid(T).name()。对于第二个问题:可以为某些类型定义特定顺序,将 std::less 保留为默认值。另一个解决方案是无序/散列映射 - 使用散列函数考虑,例如,类型名称(typeid)和对象的字符串表示,并作为最终选择器operator ==。顺便说一句,您知道无法定义 operator &lt; 的对象类别吗?
    【解决方案3】:

    您需要将 K 和 V 设为特殊对象。

    对象需要包括它是什么对象类型。

    struct {
      void *pointer;
      string type;
      // int type; // this is also possible
    } Object;
    

    上面的 Object 可以指向任何东西。但是,它还需要说明它是什么类型,因此是类型字符串。

    然后您需要能够通过读取类型中的内容将指针转换回所需的类型。

    例如。

    if (type == "int") cout << (int*)(myobject.pointer) << endl;
    

    无论如何,如果你做这样的事情,你几乎开始构建一个松散类型的解释器,因为对于你想要对对象进行的任何操作,你都需要检查它的类型(无论你是添加,连接或将值打印到标准输出)。


    如果您使用类对象并使用继承来存储您需要的任何数据,这可能会更好。

    class Object {
      public virtual string to_string() {
        return "";
      }
    };
    

    那么如果你想存储一个整数:

    class Integer : public Object {
      int i;
      public string to_string() {
        char str[50];
        sprintf(str,"%d",i);
        return string(str);
      }
      public Integer operator=(int a) {
        i=a;
        return this;
      }
    };
    

    这样你可以定义一个你希望所有对象支持的所有功能的接口。

    请注意,使基 Object 类具有虚函数意味着如果您说:

    Integer a;
    a=5;
    Object object = (Object)a;
    cout << object.to_string << endl; // prints "5"
    

    因此调用的函数是由对象的实际(真实)类型定义的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-01-18
      • 1970-01-01
      • 1970-01-01
      • 2018-04-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多