Как работают операторы преобразования в C ++? - PullRequest
33 голосов
/ 20 августа 2009

Рассмотрим этот простой пример:

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 ++? (т.е.) когда компилятор пытается заменить тип, определенный после оператора преобразования?

Ответы [ 7 ]

48 голосов
/ 20 августа 2009

Далее следуют некоторые случайные ситуации, когда функции преобразования используются и не используются.

Во-первых, обратите внимание, что функции преобразования никогда не используются для преобразования в один и тот же тип класса или в тип базового класса.

Преобразование при передаче аргумента

Для преобразования при передаче аргумента будут использоваться правила инициализации копии. Эти правила просто учитывают любую функцию преобразования, независимо от того, выполняется ли преобразование по ссылке или нет.

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

Передача аргумента - это всего лишь один контекст инициализации копии. Другой - «чистая» форма, использующая синтаксис инициализации копирования

B b = A(); // called!

Преобразование в ссылку

В условном операторе возможно преобразование в ссылочный тип, если преобразованный тип является lvalue.

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) { ... }
}

(Преобразование в bool в этом случае может быть сделано более безопасным с помощью идиомы safe-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();
16 голосов
/ 20 августа 2009

"." Оператор не перегружается в C ++. И всякий раз, когда вы говорите x.y, преобразование автоматически не будет выполняться для x.

8 голосов
/ 20 августа 2009

Конверсии не волшебство. То, что A имеет преобразование в B, а B имеет метод foo, не означает, что a.foo () будет вызывать B :: foo ().

Компилятор пытается использовать преобразование в четырех ситуациях

  1. Вы явно приводите переменную к другому типу
  2. Вы передаете переменную в качестве аргумента функции, которая ожидает другого типа в этой позиции (операторы считаются здесь функциями)
  3. Вы назначаете переменную переменной другого типа
  4. Вы используете переменную copy-construct или инициализируете переменную другого типа

Существует три типа преобразований, кроме тех, которые связаны с наследованием

  1. Встроенные преобразования (например, int-to-double)
  2. Неявная конструкция, где класс B определяет конструктор, принимающий один аргумент типа A, и не помечает его ключевым словом "явный"
  3. Определяемые пользователем операторы преобразования, где класс A определяет оператор B (как в вашем примере)

Как компилятор решает, какой тип преобразования использовать и когда (особенно когда есть несколько вариантов выбора), довольно сложно, и я бы сделал плохую работу, пытаясь сжать его в ответ на SO. В разделе 12.3 стандарта C ++ обсуждаются неявные конструкции и определяемые пользователем операторы преобразования.

(Могут быть какие-то ситуации или методы конвертации, о которых я не задумывался, поэтому, пожалуйста, прокомментируйте или отредактируйте их, если вы обнаружите, что что-то отсутствует)

6 голосов
/ 20 августа 2009

Неявное преобразование (с помощью операторов преобразования или неявных конструкторов) происходит при передаче параметров в функции (включая перегруженные операторы и операторы по умолчанию для классов). В дополнение к этому, есть некоторые неявные преобразования, выполняемые для арифметических типов (таким образом, добавление char и long приводит к добавлению двух long с длинным результатом).

Неявное преобразование не применяется к объекту, для которого выполняется вызов функции-члена: для целей неявного преобразования «this» не является параметром функции.

0 голосов
/ 14 сентября 2017

// Виртуальная таблица Fuction (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;
}
0 голосов
/ 20 августа 2009

Компилятор попытается выполнить одно (!) Пользовательское приведение (неявный оператор ctor или приведение), если вы попытаетесь использовать объект (ссылку) типа T, где требуется U.

Оператор ., однако, всегда будет пытаться получить доступ к элементу объекта (ссылка) с левой стороны. Это так, как это определено. Если вы хотите что-то более необычное, то для этого operator->() может быть перегружено.

0 голосов
/ 20 августа 2009

Вы должны сделать

((person)p).think();

Компилятор не имеет информации для автоматического приведения к человеку, поэтому вам нужно явное приведение.

Если бы вы использовали что-то вроде

person pers = p;

Тогда у компилятора есть информация для неявного приведения к персонажу.

Вы можете иметь "приведение" через конструкторы:

class A
{
public:
   A( int );
};


A a = 10; // Looks like a cast from int to A

Это несколько кратких примеров. Приведение (неявное, явное и т. Д.) Требует большего объяснения. Вы можете найти детали в серьезных книгах C ++ (см. Вопросы о книгах C ++ о переполнении стека для хороших названий, таких как this ).

...