2017-02-21 13 views
4

Возьмем следующий код:Почему стандарт C++ требует, чтобы компиляторы игнорировали вызовы операторов преобразования для базовых типов?

#include <iostream> 

struct Base { 
    char x = 'b'; 
}; 

struct Derived : Base { 
    operator Base() { return Base { 'a' }; } 
}; 

int main() { 
    Derived derived; 
    auto base = static_cast<Base>(derived); 

    std::cout << "BASE -> " << base.x << std::endl; 
} 

Под и г ++ и лязг ++, это производит:

BASE -> b 

Я ожидал следующее:

BASE -> a 

Почему? Потому что, почему я прочитал этот код, я вижу оператор преобразования внутри Derived, который возвращает экземпляр Base, содержащий 'a'.

лязг ++ оказал мне любезность испуская предупреждение:

main.cpp:9:5: warning: conversion function converting 'Derived' to its base class 'Base' will never be used 
    operator Base() { return Base { 'a' }; } 

Исследуя это предупреждение, я обнаружил, что это был дизайн (перефразировать для ясности):

class.conv.fct

тип функции преобразования ([dcl.fct]) - это «функция без параметра, возвращающего идентификатор типа преобразования». Функция преобразования никогда не используется для преобразования объекта (возможно, cv-qualified) [...] в (возможно, cv-квалифицированный) базовый класс этого типа (или ссылку на него) [...].

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

+5

Для того чтобы нам не понадобилась другая библиотечная функция 'std :: baseof', чтобы обойти умных людей,« настраивающих »полиморфное поведение? –

+0

Почему вы ожидаете изменения базы? Ничего не меняется. – juanchopanza

+0

Почему вы объявили все переменные 'volatile'? –

ответ

2

Конструктор C++ решили, что этот общий принцип должен применяться (Страуструп, C++ Язык программирования, глава 18)

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

Для лучшего или худшего, есть встроенное преобразование из Derived в Base, поэтому пользовательский оператор никогда не рассматривается.

Стандарт, как правило, следует по пути, разработанному Stroustrup, если нет веской причины не делать этого.

Страуструп приводит следующее обоснование общего типа преобразование конструкции (см. Там же):

Правило преобразования не является ни самым простым в реализации, ни простые к документу, ни наиболее общие, что может быть разработаны. Однако они значительно более безопасны, и результирующие резолюции обычно менее неожиданны, чем альтернативы. Гораздо проще вручную разрешить двусмысленность , чем найти ошибку, вызванную непредвиденным преобразованием.

+0

Спасибо, в то время как другие ответы являются хорошими и полезными, я принял это, потому что он напрямую отвечает на мой вопрос :) – OMGtechy

6

Если вы можете переопределить преобразование к базовому классу на C++, вы можете сломать множество и многое другое. Например, как именно вы сможете получить доступ к фактическому экземпляру базового класса для класса? Вам понадобится шаблон baseof, похожий на std::addressof, который используется для обхода нерегулярной перегрузки operator&.

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

4

Давайте у меня есть иерархия Animal и я хочу написать функцию, которая может быть изменена Animal, без нарезки. Как мне это сделать? У меня есть два варианта:

void by_ref(Animal&); 
void by_ptr(Animal*); 

Что я мог бы назвать как:

Dog dog = ...; 
by_ref(dog); 
by_ptr(&dog); 

Сегодня, единственное различие между этими двумя вызовами собирается быть синтаксис, используемый внутри этих двух функций и, возможно, проверки против nullptr. Это связано с тем, что Dog до Animal& и Dog*- гарантированы стандартными преобразованиями на основе базовой линии. Альтернативы нет.

Но представьте, если бы я мог написать:

struct Dog : Animal { 
    operator Animal&() { ... }; 
}; 

Теперь эти два вызова может сделать совершенно разные вещи!Мой Dog* до Animal* конверсия по-прежнему та же собака, но моя до Animal& конверсия совершенно другая Dog. Это может быть даже Cat. Который сделал бы весь этот код в принципе невозможным.

Вам потребуется специальный механизм, безусловно, даст вам базовый субобъект определенного типа:

by_ref(std::base<Animal>(dog)); 

которая в основном должна быть использованы везде, чтобы гарантировать правильность любого кода, который полагается на наследование. Который много кода.


И какую выгоду? Если вы хотите другой базы субобъекта, вы можете просто написать по-разному с именем функции:

struct Dog : Animal { 
    Animal& foo(); 
}; 

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

Смежные вопросы