【问题标题】:casting operator() - cast to reference and cast to value转换 operator() - 转换为引用并转换为值
【发布时间】:2015-11-20 15:53:32
【问题描述】:

我关注的是标题中给出的主题。

假设我们有一些非常简单的类

class Integer{
public:
    Integer(int number) : m_value(number) {}
    int value(){return m_value;}
private:
    int m_value;
};

现在我们可以使用这个类,它代表int,当然使用这个类没有更大的意义,但这只是一个例子。如果想让这个类表现得更像一个真正的整数,那么我们应该提供强制转换运算符(不包括运算符 ==、>、

operator T() {return m_value;}
operator T&() {return m_value;}

一些最简单的例子可以编译和使用它们(第一个或第二个),所以我们的类可以如下:

class Integer{
public:
    Integer(int number) : m_value(number) {}
    int value(){return m_value;}
    operator T(){return m_value;} // casting operator
private:
    int m_value;
};

但也可以:

class Integer{
public:
    Integer(int number) : m_value(number) {}
    int value(){return m_value;}
    operator T&(){return m_value;} // casting operator
private:
    int m_value;
};

同样,所以我的问题是这两个中的哪一个是合适的?当然我不能同时拥有它们,因为编译器将无法识别使用哪一个。

自然参考强制转换似乎更有效,但我不确定它是否会始终按应有的方式工作。请以某种方式告诉我,为什么一个运营商比另一个更好?

【问题讨论】:

  • 如果你返回一个引用,那么有人可以捕获它并修改类的私有数据。你想允许吗?

标签: c++ casting reference copy


【解决方案1】:

事实上,两者都需要,稍加调整,编译器就会知道选择哪个。所有神奇之处在于您可以(并且应该在有意义的情况下)将成员函数定义为 constid est 可以从常量对象调用。

因此,您的 Integer 类应该是这样的

class Integer
{
public:
    Integer() : Integer(0) {}
    Integer(int number) : m_value(number) {}
    Integer(const Integer& other) : m_value(other.value()) {}
    int value() const { return m_value; }

    operator int() const { return m_value; }
    operator int&() { return m_value; }
    const Integer& operator=(const Integer& other) { m_value = other; return *this; }
private:
    int m_value;
};

此定义将允许Integer 类在以下情况下可用:

#include <iostream>
int main()
{
    const Integer zero;
    const Integer one(1);
    Integer counter;

    counter = zero + one;
    std::cout << "counter: " << counter << std::endl;

    counter++;
    std::cout << "counter: " << counter << std::endl;

    for (Integer i=0 ; i < counter ; ++i)
    {
        std::cout << "i: " << i << std::endl;
    }

    return zero;
}

编译运行:g++ -std=c++14 -O2 -Wall -pedantic -pthread main.cpp &amp;&amp; ./a.out

输出:

counter: 1
counter: 2
i: 0
i: 1

现场示例:http://coliru.stacked-crooked.com/a/641fe02e70c02920

【讨论】:

    【解决方案2】:

    我同意以上两个答案。人们应该总是倾向于最小化直接操纵一个表示的函数的数量。 对象并通过引用公开相同的对象(为什么在上述两个答案和 cmets 中也有解释)。当确实需要访问表示时,例如 operator+=,那么可以通过固有地修改的值来实现他们在类本身中的第一个参数。

    仅根据参数值生成新值的运算符(例如 +)可以在类外部定义,并在其实现中使用基本运算符。

    class complex{
         double re,im;
         public:
         complex& operator+=(complex a);//needs access to representation.
    
    };
    complex operator+(complex a,complex b)
    {
      complex r=a;
      return r+=b; //access representation through +=
    }
    

    现在来到你的运营商,类型转换,你真的需要访问表示吗?首先,我们什么时候需要实现类型转换?举个简单的例子:

    class Cents
    {
    private:
        int m_nCents;
    public:
        Cents(int nCents=0)
        {
            m_nCents = nCents;
        }
    };
    
    
    class Dollars
        {
        private:
            int m_nDollars;
        public:
            Dollars(int nDollars=0)
            {
                m_nDollars = nDollars;
            }
    
             // Allow us to convert Dollars into Cents
             operator Cents() { return Cents(m_nDollars * 100); }
        };
    

    据我说,这里我们不需要访问表示,也不需要通过引用返回。

    上面所有的例子都处理了一个非常小的对象。但是当处理复制操作确实非常昂贵的重对象时,由程序员来决定。如果我们想避免复制成本,我们可以传递引用并返回引用但是...

    1. 对结果的引用将作为 引用返回值,返回值不能是 自动变量。

    2. 由于一个运算符经常在表达式中多次使用,因此 结果不能作为静态局部变量。

    因此,结果通常会分配到免费存储中。复制 返回值通常比分配和(最终)释放空闲存储中的对象便宜(在执行时间、代码空间和数据空间方面)。(#11.6 over.large #C++ Programming Language)

    这些话题总是有争议的,但我们需要确保的是安全性和没有泄漏,这会导致更多的控制和更少的痛苦。

    【讨论】:

      【解决方案3】:

      您的示例中定义的引用转换相当无用 - 您将可修改的引用返回给您的内部成员,因此违反了封装原则。

      如果您改为返回 const 引用,这将不是问题。至于intconst int&amp; 之间的选择,这并不重要——它们在所有情况下的行为都完全相同。

      如果你定义一个重类的转换,这会更有趣。这样做时,您将看到两个选项 - 通过 const 引用返回 - 但这意味着,不可能在返回值上调用非 const 限定方法。如果按价值返回,您将产生复制构造器的成本。选择将由类设计决定。

      还有一个返回引用的生命周期问题 - 但这通常不是问题,除非代码变得非常棘手(您需要将返回的引用存储为类的成员才能解决这个问题)。

      【讨论】:

      • 好的,但是假设我想以任何一种方式公开私人成员。从架构的角度来看,我知道这不好,但是如果我想以尽可能简单的方式使用 Integer 类,那么我可能想将它传递给函数并允许某人更改此值。那么返回普通参考可以吗?
      • 拥有 Int 类的意义何在?如果您不关心保护内部成员,只需使用普通 int 并避免任何不必要的工作。
      • 来自OP的问题:“现在我们可以使用这个类,它代表int,当然使用这个类没有更大的意义,但这只是一个例子。”
      • @YSC,这将适用于任何此类。如果您愿意公开原始内部成员,则不需要包装器 - 只需使用内部成员类型。
      • @SergeyA 除非您需要聚合属于一起的多个对象。在这种情况下,确实需要将它们夹在一个包装中,但不需要保护它们免受操纵,因为它们有自己的保护机制。而且我相信我们可以提出其他好的例子。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-12-03
      • 2019-10-16
      • 2019-09-15
      • 2017-08-23
      • 1970-01-01
      相关资源
      最近更新 更多