C ++ литой эквивалент перегрузки - PullRequest
3 голосов
/ 15 ноября 2011

Интересно, есть ли в C ++ возможность достичь такой же перегрузки приведения, как в этом примере C #:

class A {
    public static implicit operator A(string s) {
        return new A();
    }
    public static implicit operator A(double d) {
        return new A();
    }        
    static void Main(string[] args) {
        A a = "hello";
        A b = 5.0;
    }
}

В C ++ должно быть что-то вроде этого:* Можете ли вы помочь мне, как я могу сделать эту перегрузку броска?

Ответы [ 5 ]

16 голосов
/ 15 ноября 2011

Обычно это достигается с помощью конструкторов :

class A
{
public:
  A(const std::string & s) { /*...*/ }
  A(double d)              { /*...*/ }
  //...
};

Использование: A a("hello"), b(4.2), c = 3.5, d = std::string("world");

(Если вы объявите конструктор explicit, то только первыйФорма («прямая инициализация», с круглыми скобками) разрешена. В противном случае две формы полностью идентичны.)

Конструктор с одним параметром также называется «конструктором преобразования» по этой самой причине: вы можете использоватьэто построить объект из некоторого другого объекта, то есть "преобразовать" double, скажем, в A.

. Неявное преобразование широко используется.Например, он будет применяться в следующей ситуации:

void f(A, const A&) { /* ... */ }

int main() { f(1.5, std::string("hello")); }

Создает временные значения A(1.5) и A("hello") и передает их f.(Временные привязки также связываются с постоянными ссылками, как вы можете видеть со вторым аргументом.)

Обновление: (Кредиты для @cHao для поиска.) Согласно стандарту (12.3.4) «Не более одного пользовательского преобразования (конструктор или функция преобразования) неявно применяется к одному значению».(Это относится к неявному преобразованию; прямая инициализация а-ля A a("hello"); не подпадает под это *. См. этот связанный вопрос .) К сожалению, вы не можете сказать f("hello", "world");.(Конечно, вы можете добавить конструктор const char *, который в новом C ++ 11 вы даже можете пересылать в строковый конструктор практически без усилий.)

*) Я думаю, что это на самом деленемного более тонкий.Правило влияет только на определенные пользователем преобразования .Итак, во-первых, вы получаете фундаментальное преобразование из char (&)[6] в const char * бесплатно.Затем вы получаете одно неявное преобразование из const char * в std::string.Наконец, у вас есть явная конструкция преобразования из std::string в A.

2 голосов
/ 15 ноября 2011

Оператор преобразования обеспечит неявное преобразование вашего типа в какой-либо другой тип.Например:

class A
{
public:
  operator std::string () const { return "foo"; }
};

Однако неявные преобразования могут быть опасными , потому что они могут быть использованы, когда вы не ожидаете, что они будут использованы, и предпочтут ошибку компилятора.Поэтому часто лучше реализовать конструктор преобразования в целевом объекте, а не оператор преобразования в исходном объекте.

class A
{ 
public:
  A(const std::string& rhs) { /* ... */ }
};

Однако с этим все еще возможно неявное преобразование.Обратите внимание:

string f = "foo";
A foo = f;

f неявно преобразуется в A, потому что конструктор преобразования доступен.Однако, может быть, это приведет к ошибке компилятора.Помечая конструктор преобразования explicit, вы делаете это так, что вы можете легко конвертировать из одного типа в другой, но только , когда вы действительно намеревались.

#include <string>

using namespace std;

class A
{
public:
    A() {};
    explicit A(const std::string& ) {};
};

int main()
{
    string f = "foof";
    A a1 = f;   // WONT COMPILE
    A a(f);     // WILL COMPILE
}
1 голос
/ 15 ноября 2011

Нет необходимости в приведении ... это можно легко сделать, создав для вашего класса конструкторы, которые принимают правильный тип аргумента, и только использует один аргумент, поэтому они могут вызываться неявно компилятор.

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

Кроме того, если вы когда-нибудь захотите избежать неоднозначности в выборе конструкторов, основанных на предполагаемом преобразовании типов, вы всегда можете использовать ключевое слово explicit со своими конструкторами. Например, представьте сценарий, подобный следующему:

 struct test
 {
     int a;

     test (signed int b): a(b) {}
     test (unsigned int b): a(b) {}
 };

 int main()
 {
     test A = 5.0;

     return 0;
 }

Это приведет к ошибке компилятора из-за неоднозначности при преобразовании типа double в тип масштабирования ... тип double может быть неявно преобразован в оба типа. Это можно исправить с помощью ключевого слова explicit, чтобы выполнить следующие действия:

struct test
{
    int a;

    test (signed int b): a(b) {}
    explicit test (unsigned int b): a(b) {}
};

Теперь версия конструктора unsigned int для test будет вызываться только в том случае, если ей передано unsigned int. Для операции преобразования типа double будет использоваться версия по умолчанию int, что устраняет проблему неоднозначности.

0 голосов
/ 15 ноября 2011

Что это значит в C #?

В C # семантика не связана с перегрузкой operator = (чего вы не можете), но предоставляет оператор приведения любого типа к типу A или от типа A к любому типу.

Это означает следующий код (дополненный преобразованием A-типа):

class A {
    public static implicit operator A(string s) {
        return new A();
    }
    public static implicit operator A(double d) {
        return new A();
    }        
    public static implicit operator string(A a) {
        return string.Empty;
    }
    public static implicit operator double(A a) {
        return 0.0;
    }        
    static void Main(string[] args) {
        A a = "hello";    // line A
        A b = 5.0;        // line B
        a = "World";      // line C
        a = 3.14;         // line D
        double d = a ;    // line E
        string s = a ;    // line F
    }
}

работает для присваивания, либо в простых присваиваниях (например, строки C и D), либо в присваиваниях в объявлениях (например, строки A и B). Строки E и F показывают, как мы можем преобразовать пользовательский тип в другие типы.

Как это сделать в C ++?

В C ++ строки A и B являются объектными конструкциями, которые будут вызывать соответствующие конструкторы.

Строки C и D являются присвоением, которое вызывает соответствующий оператор присвоения.

Таким образом, код C ++ для предоставленного вами кода C # должен содержать как конструкторы, так и присваивания для строки и типа double, как показано ниже:

class A
{
    public:
        A(const std::string & s) { /*...*/ }
        A(double d)              { /*...*/ }

        A & operator = (const std::string & s) { /*...*/ ; return *this ; }
        A & operator = (double d)              { /*...*/ ; return *this ; }

        // etc.
} ;

Таким образом, вы можете иметь

void main()
{
    A a0 = "Hello" ;   // constructor
    A a1("Hello") ;    // constructor
    A a2 = 5.0 ;       // constructor
    A a3(5.0) ;        // constructor


    a0 = "Hello World" ; // assignment
    a0 = 3.14 ;          // assignment
}

А как насчет операторов приведения в C ++?

Операторы приведения в C ++ работают подобно следующим операторам преобразования C #:

class A
{
    static public operator string(A a)   { /*... ; return a string */ }
    static public operator double(A a)   { /*... ; return a double */ }

        // etc.
}

В C ++ операторы приведения записываются так:

class A
{
    public:
        operator std::string()   { /*... ; return a string */ }
        operator double()        { /*... ; return a double */ }

        // etc.
} ;

void main()
{
    A a ;
    std::string s ;
    double d ;

    // etc.

    s = a ;    // cast operator
    d = a ;    // cast operator
}

Почему это так сложно в C ++?

В C # оператор приведения / преобразования может быть записан из типа класса в любой тип или из любого типа в тип класса.

В C ++ для преобразования вы должны выбрать один или несколько способов, а именно: конструктор, оператор присваивания или оператор приведения, потому что в C ++ вы имеете детальный контроль над типами и операциями, и, следовательно, вы должны использовать этот мелкозернистый API.

Конструкции означают, что объект не существует, поэтому нет смысла конструировать его по какому-либо значению по умолчанию, а затем присвоить ему другое значение (это может стоить скорости). В C # тип ссылки / значения равен нулю / обнуляется перед присваиванием, поэтому это не проблема.

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

Что касается приведения, он используется в текущем случае для преобразования существующего класса в другой, для которого у вас нет контроля: вы не имеете права расширять std :: string, чтобы разместить конструктор для вашего класса, и нет способа добавить конструктор во встроенный тип, такой как double.

0 голосов
/ 15 ноября 2011

Вы можете предоставить конструктору один аргумент нужного вам типа:

class Foo {

  int bar;

public:
  Foo(const int bar) : bar(bar) {};

}

Foo foo = 5;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...