【问题标题】:How do conversion operators work in C++?转换运算符如何在 C++ 中工作?
【发布时间】:2009-08-20 18:03:26
【问题描述】:

考虑这个简单的例子:

template <class Type>
class smartref {
public:
    smartref() : data(new Type) { }
    operator Type&(){ return *data; }
private:
    Type* data;
};

class person {
public:
    void think() { std::cout << "I am thinking"; }
};

int main() {
    smartref<person> p;
    p.think(); // why does not the compiler try substituting Type&?
}

转换运算符如何在 C++ 中工作? (即)编译器何时尝试替换转换运算符之后定义的类型?

【问题讨论】:

    标签: c++ conversion-operator


    【解决方案1】:

    以下是一些使用和不使用转换函数的随机情况。

    首先,请注意转换函数永远不会用于转换为相同的类类型或基类类型。

    参数传递期间的转换

    参数传递期间的转换将使用复制初始化的规则。这些规则只考虑任何转换函数,不管是否转换为引用。

    struct B { };
    struct A {
      operator B() { return B(); }
    };
    void f(B);
    int main() { f(A()); } // called!
    

    参数传递只是复制初始化的一种上下文。另一种是使用复制初始化语法的“纯”形式

    B b = A(); // called!
    

    转换为参考

    在条件运算符中,如果转换为左值的类型,则可以转换为引用类型。

    struct B { };
    struct A {
      operator B&() { static B b; return b; }
    };
    
    int main() { B b; 0 ? b : A(); } // called!
    

    另一个到引用的转换是当你直接绑定一个引用时

    struct B { };
    struct A { 
      operator B&() { static B b; return b; }
    };
    
    B &b = A(); // called!
    

    转换为函数指针

    您可能有一个转换为函数指针或引用的函数,并且当进行调用时,它可能会被使用。

    typedef void (*fPtr)(int);
    
    void foo(int a);
    struct test {
      operator fPtr() { return foo; }
    };
    
    int main() {
      test t; t(10); // called!
    }
    

    这个东西有时实际上会变得非常有用。

    转换为非类类型

    无处不在的隐式转换也可以使用用户定义的转换。您可以定义一个返回布尔值的转换函数

    struct test {
      operator bool() { return true; }
    };
    
    int main() {
      test t;
      if(t) { ... }
    }
    

    (在这种情况下,通过safe-bool idiom 可以更安全地转换为bool,以禁止转换为其他整数类型。)在内置运算符需要某种类型的任何地方都会触发转换。不过,转换可能会妨碍您。

    struct test {
      void operator[](unsigned int) { }
      operator char *() { static char c; return &c; }
    };
    
    int main() {
      test t; t[0]; // ambiguous
    }
    
    // (t).operator[] (unsigned int) : member
    // operator[](T *, std::ptrdiff_t) : built-in
    

    调用可能不明确,因为对于成员,第二个参数需要转换,而对于内置运算符,第一个参数需要用户定义的转换。其他两个参数分别完美匹配。在某些情况下,调用可以是明确的(ptrdiff_t 需要与 int 不同)。

    转换函数模板

    模板允许一些好东西,但最好对它们非常谨慎。以下使类型可转换为任何指针类型(成员指针不被视为“指针类型”)。

    struct test {
      template<typename T>
      operator T*() { return 0; }
    };
    
    void *pv = test();
    bool *pb = test();
    

    【讨论】:

    • 很好的解释,非常感谢 :) 如果我明白你在说什么,那么我的代码无法编译的原因是由于 smartref 和 person有点运算符。我认为这与您转换为非类类型的第二个示例几乎相同,对吧?
    • 实际上,您可能会注意到 litb 的代码在任何地方都没有使用“.”操作员。所以我认为它不能解决你原来的问题。
    • @Neil 他最初的问题是“转换运算符如何在 C++ 中工作?(即)编译器何时尝试替换转换运算符之后定义的类型?”。
    • 来自原代码:p.think(); // 为什么编译器不尝试替换 Type&?这肯定是在问为什么 p(点之前的东西)没有被转换。
    • @AraK,正如 Neil 所说,当您执行“x.y”时没有应用转换 - 设计使然。当然,允许这样的转换会有问题,其中包括是否访问 smartref 或个人成员的问题。但我的回答并没有关注这个“x.y”问题——我专注于展示编译器使用转换函数时的其他情况,因为这就是我理解你的主要问题的方式。
    【解决方案2】:

    “。”运算符在 C++ 中不可重载。并且每当您说 x.y 时,都不会自动对 x 执行转换。

    【讨论】:

      【解决方案3】:

      转化不是魔法。仅仅因为 A 有一个到 B 的转换并且 B 有一个 foo 方法并不意味着 a.foo() 会调用 B::foo()。

      编译器尝试在四种情况下使用转换

      1. 您将变量显式转换为另一种类型
      2. 您将变量作为参数传递给函数,该函数在该位置需要不同的类型(操作符在这里算作函数)
      3. 您将变量分配给不同类型的变量
      4. 您使用变量复制构造或初始化不同类型的变量

      除了与继承有关的转换之外,还有三种类型的转换

      1. 内置转换(例如 int 到 double)
      2. 隐式构造,其中 B 类定义了一个采用 A 类型的单个参数的构造函数,并且不使用“显式”关键字对其进行标记
      3. 用户定义的转换运算符,其中 A 类定义了运算符 B(如您的示例中所示)

      编译器如何决定使用哪种类型的转换以及何时(尤其是当有多种选择时)非常复杂,而且我在试图将其浓缩为关于 SO 的答案方面做得很糟糕。 the C++ standard 的第 12.3 节讨论了隐式构造和用户定义的转换运算符。

      (可能有一些我没有想到的转换情况或方法,如有遗漏请评论或编辑)

      【讨论】:

        【解决方案4】:

        在将参数传递给函数(包括类的重载和默认运算符)时会发生隐式转换(无论是通过转换运算符还是非显式构造函数)。除此之外,还有一些对算术类型执行的隐式转换(因此添加一个 char 和一个 long 会导致两个 long 相加,得到一个 long 结果)。

        隐式转换不适用于对其进行成员函数调用的对象:出于隐式转换的目的,“this”不是函数参数。

        【讨论】:

          【解决方案5】:

          如果您尝试使用 T 类型的对象(引用),其中需要 U,编译器将尝试一个(!)用户定义的转换(隐式 ctor 或转换运算符)。

          但是,. 运算符将始终尝试访问其左侧的对象(引用)的成员。这就是它的定义方式。如果你想要更花哨的东西,那operator-&gt;() 可以被重载。

          【讨论】:

          • 需要注意的是operator-&gt;只能被定义为一个成员函数,因此@987654326右边的表达式不会有任何转换(用户定义的或其他的) @.
          • 是的,我知道。但他想要的(智能引用)是智能指针的兄弟,这在 C++ 中无法完成,因为不能重载 . 运算符。这就是我指出operator-&gt; 的原因。不过,我想我不是很清楚。
          【解决方案6】:

          你应该这样做

          ((person)p).think();
          

          编译器没有自动转换为人的信息,所以你需要显式转换。

          如果你会使用类似的东西

          person pers = p;
          

          那么编译器就有了隐式转换为 person 的信息。

          您可以通过构造函数进行“强制转换”:

          class A
          {
          public:
             A( int );
          };
          
          
          A a = 10; // Looks like a cast from int to A
          

          这些是一些简短的例子。转换(隐式、显式等)需要更多解释。您可以在严肃的 C++ 书籍中找到详细信息(请参阅关于堆栈溢出的 C++ 书籍的问题,以获得好的标题,例如 this one)。

          【讨论】:

          • 这可以通过 C++ 强制转换来改进。
          【解决方案7】:

          //虚拟表函数(VFT)

          #include <iostream>
          
          using namespace std;
          
          class smartref {
          public:
          virtual char think() { }//for Late bindig make virtual function if not make virtual function of char think() {} then become early binding and pointer call this class function 
              smartref() : data(new char) { }
            operator char(){ return *data; }
          private:
              char* data;
          };
          
          class person:public smartref
          {
          public:
              char think() { std::cout << "I am thinking"; }
          };
          
          int main() {
              smartref *p;//make pointer of class
              person o1;//make object of class
              p=&o1;//store object address in pointer
              p->think(); // Late Binding in class person
          return 0;
          }
          

          【讨论】:

          • 这是一个没有解释的答案,实际上并没有解决问题,即转换运算符如何工作以及它们是否可用于调用转换类型的成员函数。从一个类继承与转换到它相差甚远,还有 OT。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-01-27
          • 2020-06-17
          相关资源
          最近更新 更多