有没有一种方便的方法来将std :: pair包装为新类型?

时间:2008-10-14 18:48:08

标签: c++ stl

我经常发现自己使用std :: pair来定义两个相关量的逻辑分组作为函数参数/返回值。一些例子:row / col,tag / value等。

我常常应该滚动自己的类而不是仅仅使用std :: pair。很容易看出事情何时开始崩溃 - 当代码变得乱七八糟时,第一,第二,很难记住什么是什么 - std::pair<int, int>传达的意义少于类型Position

你发现什么是将std :: pair的功能包装在一个传达真正含义的类型中的最佳方法?

以下是我考虑过的一些事情:

typedef std::pair<int, int> Position;

这至少在传递类型时给类型一个有意义的名称,但是类型没有强制执行,它仍然只是一对,并且大多数相同的问题仍然存在。然而,编写起来非常简单。

struct Position : public std::pair<int, int>
{
    typedef std::pair<int, int> Base;
    Position() : Base() {}
    Position(const Position &x) : Base(x) {}
    Position(int a, int b) : Base(a, b) {}

    int &row() { return first; }
    const int &row() const { return first; }

    int &col() { return second; }
    const int &col() const { return second; }
};

这样更好,因为我们可以通过合理描述性的名称访问变量。这里的问题是你仍然可以访问第一个和第二个,因此抽象容易泄漏。此外,通过函数访问简单变量会使语法烦人。

显而易见的下一步是将继承设为私有:

struct Position : private std::pair<int, int>
{
    typedef std::pair<int, int> Base;
    Position() {}
    Position(const Position &x) : Base(x) {}
    Position(int a, int b) : Base(a, b) {}

    int &row() { return first; }
    const int &row() const { return first; }

    int &col() { return second; }
    const int &col() const { return second; }

    bool operator<(const Position &x) const { return Base(*this) < Base(x); }
    // other forwarding operators as needed...
};

所以现在至少我们已经摆脱了对第一和第二的访问权限,但现在又出现了一个新问题。当我们想要将类型存储在std :: set中时,我们现在无法访问运算符&lt;超载,因为我们无法访问第一个和第二个。这意味着我们必须为每个我们想要的运算符重载定义转发函数。对我来说,这通常是==,!=和&lt;,但可能还有其他我想要的。是的我知道我可能不应该重载运算符&lt;只是将它粘贴在一个关联容器中,但它使一切变得如此简单......为每种新类型定义这些运算符是一种痛苦,我们仍然必须通过函数访问。我们可以解决这个问题:

struct Position
{
    Position() {}
    Position(const Position &x) : row(x.row), col(x.col) {}
    Position(int row, int col) : row(row), col(col) {}

    int row, col;
};
bool operator<(const Position &a, const Position &b)
{
    return a.row < b.row || (!(b.row < a.row) && a.col < b.col);
}
// more overloads as needed

所以现在我们有了简单的变量访问,但是现在定义重载的运算符更加困难,因为我们实际上每次都必须重新实现它们,而不是仅将它们转发给它们...

是否有任何我忽略的解决方案,使这很容易没有缺点?如果没有你更喜欢哪一个?

8 个答案:

答案 0 :(得分:6)

这就是Boost.Tuple的目的。

但你现在可能应该使用std::tuple ......

答案 1 :(得分:4)

一位同事向我指出了两种可能的解决方案:

使用boost strong typedef作为typedef的改进版本。我以前从来没有听说过这个,它似乎并不真正属于任何子库,只是浮动。

使用宏来生成不同运算符所需的代码。这样我就不必在每个定义级别上明确地写任何东西,只需执行类似DEFINE_PAIR_TYPE(Position, int, int, row, col);的操作。这可能与我正在寻找的最接近,但与其他人提出的一些解决方案相比,它仍然有点邪恶。

答案 2 :(得分:3)

还有Boost::Operators库可自动生成操作员代码。它类似于Martin York suggested的SGI库,但可能更便携。

答案 3 :(得分:2)

您仍然可以通过转发来重复使用pair功能:

bool operator< ( const Position &a, const Position &b ) 
{
    return
        std::make_pair( a.row, a.col ) < std::make_pair( b.row, b.col );
}

虽然你最终仍然需要为你需要的每一个操作都这样做......

答案 4 :(得分:2)

您可以使用一些标准实用程序模板来帮助定义关系运算符。

#include&lt; utility&gt;

http://www.sgi.com/tech/stl/operators.html

类型要求

对operator!=的要求是x == y是有效的表达式
对运营商的要求&gt;那是y&lt; x是有效的表达式
对运算符&lt; =的要求是y&lt; x是有效的表达式
运算符&gt; =的要求是x&lt; y是有效的表达式

所以基本上它会自动生成其他运算符给出&lt;和==所有你要做的就是包括&lt; utility&gt;

答案 5 :(得分:1)

我必须说,为了制作一个简单的结构,我们需要考虑很多。

重载运算符&lt;和operator ==你已经完成了。我使用它来编写很多代码,主要是因为我通常要存储的成员变量多于2。

struct MyStruct
{
    std::string var1;
    std::string var2;
    bool var3;

    struct less : std::binary_function<struct MyStruct, struct MyStruct, bool>
    {
        bool operator() (const struct MyStruct& s1, const struct MyStruct& s2) const
            { if (var1== a2.var1) return var2 < a2.var2; else return var3 < a2.var3; }
    };
};
typedef std::set<struct MyStruct, MyStruct::less> MySet;

或将这些放在类定义

bool operator==(const MyStruct& rhs) const 
    { return var1 == rhs.var1 && var2 == rhs.var2 && var3 == rhs.var3; };
bool operator<(const MyStruct& a2) const  
    { if (var1== a2.var1) return var2 < a2.var2; else return var3 < a2.var3; };

最好的理由是它易于理解上述内容,它们可以轻松地放入类定义中,如果您发现以后需要更多变量,它们很容易扩展。当有一个更简单的解决方案时,我永远不会尝试重载std :: pair。

答案 6 :(得分:1)

不要使用它。

我完全因为这个原因而讨厌std :: pair。你永远不知道哪个是哪个,因为第一个和第二个访问都是公开的,你也不能强制执行合同。

但毕竟,这是一个品味问题。

答案 7 :(得分:0)

不幸的是strong typedefs不会进入C++0x,它已被赋予未准备好进行C ++ 0x的分类,但未来可以重新提交。< / p>