Почему auto_ptr нарушает частное наследование в Visual C++?

Справочная информация: это было обнаружено в Visual Studio 2008 и снова подтверждено в Visual Studio 2013. G++ закричал в коде, в то время как Visual молча принял частное нарушение наследования.

Итак, на Visual C++ у нас есть следующий код:

class Base {};
class Derived : Base {};      // inherits privately. Adding explicitly the
                              //    keyword private changes nothing

int main()
{
   std::auto_ptr<Base>(new Derived) ;   // compiles, which is NOT EXPECTED
   std::auto_ptr<Base> p(new Derived) ; // Does not compile, which is expected
}

Почему первый (временный) auto_ptr компилируется? Я вошел в него в отладке, он сделал именно то, что должен был сделать с публичным наследованием (вызов правильного конструктора и т. Д.)

Интересно, возможно ли проблема была в реализации auto_ptr (мы никогда не узнаем...), я уменьшил проблему в этом автономном коде:

class Base {};
class Derived : Base {};

template <typename T>
class Ptr
{
   T * m_p;

   public :
      Ptr(T * p_p)
         : m_p(p_p)
      {
      }
} ;

int main()
{
   Ptr<Base>(new Derived) ;   // compiles, which is NOT EXPECTED
   Ptr<Base> p(new Derived) ; // Does not compile, which is expected
}

Опять же, я ожидал, что код НЕ скомпилируется, поскольку Derived наследуется от Base.

Но когда мы создаем временный, это работает.

И мы не можем винить в этом std::auto_ptr.

Что-то в стандарте (или 98, или 11, или 14) я пропустил, или это ошибка?

1 ответ

Решение

Derived* -До- Base* преобразование, даже если наследование является частным, разрешено в C-стиле и функциональных приведениях. И нет, это не значит, reinterpret_cast в таком случае.

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

5.2.3 Явное преобразование типов (функциональная запись) [expr.type.conv]

1 [...] Если список выражений является одним выражением, выражение преобразования типа эквивалентно (в определенности и если определено в значении) соответствующему приведенному выражению (5.4). [...]

5.4 Явное приведение типов (нотация приведения) [expr.cast]

4 Преобразования, выполненные

  • const_cast (5.2.11),
  • static_cast (5.2.9),
  • static_cast с последующим const_cast,
  • reinterpret_cast (5.2.10) или
  • reinterpret_cast с последующим const_cast,

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

  • указатель на объект производного типа класса или lvalue или rvalue производного типа класса может быть явно преобразован в указатель или ссылку на однозначный тип базового класса, соответственно;
  • [...]

В вашей ситуации компилятор интерпретирует это как static_cast от Derived* в auto_ptr<Base> и в этом static_cast указатель на объект типа производного класса преобразуется в указатель однозначного типа базового класса. Так что, похоже, стандарт это позволяет.

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

Вы можете сообщить об этом как об ошибке. Из комментария Csq мы узнаем, что есть связанный отчет, в котором static_cast также позволяет это преобразование, но это не совсем то же самое. В этом случае преобразование из Derived* в Base* явный, но здесь он неявный, и Visual C++ обычно отклоняет его в неявных преобразованиях.

Обратите внимание, что в функциональных приведениях, использующих несколько выражений, это неверное толкование невозможно: компилятор правильно отклоняет следующее:

class Base { };
class Derived : Base { };

template <typename T>
class Ptr {
public:
  Ptr(T *a, T *b) { }
};

int main() {
  Ptr<Base>(new Derived, new Derived);
  // error C2243: 'type cast' : conversion from 'Derived *' to 'Base *' exists, but is inaccessible
}
Другие вопросы по тегам