【问题标题】:C++ relational operators generatorC++ 关系运算符生成器
【发布时间】:2014-04-30 12:57:41
【问题描述】:

一旦定义了< 运算符,您就可以估计其余关系运算符的行为方式。我正在尝试为我的课程实现一种方法。

我想要的是只定义 < 和其他隐式默认的运算符。到目前为止我得到的是这个设计,我将在下面详细说明:

template<typename T>
struct relational
{
    friend bool operator> (T const &lhs, T const &rhs) { return rhs < lhs; }
    friend bool operator==(T const &lhs, T const &rhs) { return !(lhs < rhs || lhs > rhs); }
    friend bool operator!=(T const &lhs, T const &rhs) { return !(rhs == lhs); }
    friend bool operator<=(T const &lhs, T const &rhs) { return !(rhs < lhs); }
    friend bool operator>=(T const &lhs, T const &rhs) { return !(lhs < rhs); }
};

因此,对于实现 &lt; 运算符的类,它只需要从 relational 继承即可默认其余运算符。

struct foo : relational<foo>
{ 
    // implement < operator here
};
  1. 有没有更好的设计替代品?
  2. 这段代码中有定时炸弹吗?我假设如果用户想要为其中一个运算符定义自定义实现,重载决议将启动并选择非模板(用户定义)实现。如果不是这种情况(或者我会遇到从relational 继承的类模板的问题),我应该像这样实现relational 中的运算符吗?

    // inside the relational struct
    friend bool operator>(relational const &lhs, relational const &rhs)
    { // functions that involve implicit conversion are less favourable in overload resolution
            return (T const&)rhs < (T const&)lhs; 
    }
    

感谢您的建议,这里是demo of the code working

【问题讨论】:

  • 您应该注意,朋友关系不会被继承。我不知道这是否会在某个时候打击你。
  • 旁注:rhs 表示“右手边”,lhs 表示“左手边”。看来您的函数参数是反向命名的。此外,虽然根据operator &lt; 定义operator == 在数学上可能很有趣,但它很可能对性能不友好,因为您调用operator &lt; 两次,而实际操作可能与单个operator == 非常相似。跨度>
  • 1.您部分复制了Boost.Operatorsstd::rel_ops。 2. 如果您允许 2 个原始操作,您会发现更多客户端:&lt;==。在某些情况下,&lt; 的排序不是全部,即!(a&lt;b) &amp;&amp; !(a&gt;b) &amp;&amp; a!=b
  • @Angew 客户端只会是你自己写的类(或者知道这个类存在的人写的),所以你可以走得更远,使用命名函数。为什么要特权&lt;
  • @JamesKanze 因为这就是std::rel_ops 所做的:-) 但你是对的,当然没有根本原因,正如你在回答中所证明的那样(我已经赞成)。跨度>

标签: c++ templates


【解决方案1】:

我通常使用从罗伯特·马丁那里学到的技巧来做到这一点。 我有一个模板类:

template <typename T>
class ComparisonOperators
{
protected:
    ~ComparisonOperators() {}

public:
    friend bool operator==( T const& lhs, T const& rhs )
    {
        return lhs.compare( rhs ) == 0;
    }
    friend bool operator!=( T const& lhs, T const& rhs )
    {
        return lhs.compare( rhs ) != 0;
    }
    friend bool operator<( T const& lhs, T const& rhs )
    {
        return lhs.compare( rhs ) < 0;
    }
    friend bool operator<=( T const& lhs, T const& rhs )
    {
        return lhs.compare( rhs ) <= 0;
    }
    friend bool operator>( T const& lhs, T const& rhs )
    {
        return lhs.compare( rhs ) > 0;
    }
    friend bool operator>=( T const& lhs, T const& rhs )
    {
        return lhs.compare( rhs ) >= 0;
    }
};

需要操作符的类派生自:

class Toto : public ComparisonOperators<Toto>
{
    // ...
public:
    //      returns value < 0, == 0 or >0, according to
    //      whether this is <, == or > other.
    int compare( Toto const& other ) const;
};

(我的实现实际上有点复杂,因为它 使用一些简单的元编程来调用isEqual,而不是 compare,如果该函数存在。)

编辑:

并重新阅读您的问题:这基本上就是您正在做的事情,这几乎是这类事情的标准习语。我更喜欢使用像compare 这样的命名函数,但这只是个人喜好。然而,处理isEqual 的元编程技巧值得费心:这意味着您可以对仅支持相等的类型使用相同的类;当编译器尝试实例化时,你会得到一个错误,例如operator&lt;=,但编译器不会尝试实例化它,除非有人使用它。通常情况下,isEqual 可以比compare 更有效地实现。

编辑 2:

为了它的价值:我系统地做这件事。我也有 ArithmeticOperators(根据+=定义例如+), MixedTypeArithmeticOperators(和上面一样,但是有两个 类型,T1,它是基类,T2;它 提供所有运算符的组合)。和 STLIteratorOperators,实现了 STL 迭代器 界面基于更合理、更容易上手的东西 实现(基本上,带有isEqual 的 GoF 迭代器 功能)。他们节省了很多样板文件。

编辑 3:

最后:我只是查看了工具包中的实际代码。 有条件地支持isEqual 甚至比 我记得:上面的模板类有一个公共成员:

bool isEqual( T const& other ) const
{
    return static_cast< T const* >( this )->compare( other ) == 0;
}

operator==operator!= 只需使用isEqual,不 涉及模板元编程。如果派生类 定义了一个isEqual,它隐藏了这个,然后被使用了。如果 不,这个被使用了。

【讨论】:

  • 对于STLIteratorOperators 的事情,boost::iterator_facade 怎么样?
  • @Xeo 我写STLIteratorOperators 的时候它不在。我怀疑它确实提供了相同的功能,但可能具有一些额外的复杂性。 (我发现在项目中添加 Boost 可以显着增加构建时间。)
  • 当然是受保护的析构函数!如果通过指向基类的指针进行删除,则可以避免破坏破坏。此添加和带有compare 的扩展证明了 +1 和接受的答案。
  • @NikosAthanasiou 关于受保护的析构函数,是的。在实践中,很难想象有人拥有指向基类的指针,更不用说通过它删除了,但是受保护的析构函数接近免费(在 C++11 中,= default 是免费的),所以为什么不添加保护。
【解决方案2】:

朋友不是继承的,所以这个想法行不通。但是,您可以巧妙地使用宏来代替,例如:

#define GEN(X) \
    friend bool operator> (T const &lhs, T const &rhs) { return rhs < lhs; } \
    friend bool operator==(T const &lhs, T const &rhs) { return !(lhs < rhs || lhs > rhs); } \
    friend bool operator!=(T const &lhs, T const &rhs) { return !(rhs == lhs); } \
    friend bool operator<=(T const &lhs, T const &rhs) { return !(rhs < lhs); } \
    friend bool operator>=(T const &lhs, T const &rhs) { return !(lhs < rhs); }

你可以用作:

class Foo
{
...
GEN(foo)
};

【讨论】:

  • 因为这里的友谊不是为了访问,而只是为了允许在类定义中内联定义一个非成员函数,我不明白为什么会出现问题。
  • 因为您正在尝试继承,而这并不完全是内联
  • 这里的继承没有什么违法的。由于T::operator&lt; 是公开的,因此该函数可以访问它,即使它不是T 的朋友。他示例中的运算符被声明为friend 的原因是因为这是您可以在类定义中提供非成员函数实现的唯一方法。
  • @JamesKanze 你说了什么,我想补充一件事。友情有一个常见的成语叫做“朋友注射”(什么的)。它的作用是在实例化类型时生成函数。这个答案不是我想要的,但它有效并且得到了我的 +1!
  • @NikosAthanasiou 叫朋友名字注入(标准中没有,但一般来说)。理解起来有点复杂:友元函数的 name 是在周围的命名空间中声明的,但在那里不可见;它只能由 ADL 找到,它在类定义(包括基类)中查找。
猜你喜欢
  • 2018-05-30
  • 1970-01-01
  • 2017-09-27
  • 1970-01-01
  • 2011-03-27
  • 2021-11-08
  • 1970-01-01
  • 1970-01-01
  • 2013-03-21
相关资源
最近更新 更多