Используется виртуальная функция-член, если она не чистая?

C++03 3.2.2 ... Используется объект или неперегруженная функция, если ее имя появляется в потенциально оцениваемом выражении. Виртуальная функция-член используется, если она не чистая ...

А затем в 3.2.3 мы имеем: Каждая программа должна содержать ровно одно определение каждой не встроенной функции или объекта, которые используются в этой программе; диагностика не требуется. Определение может появиться в программе явно, его можно найти в стандартной или пользовательской библиотеке или (при необходимости) оно определено неявно (см. 12.1, 12.4 и 12.8). Встроенная функция должна быть определена в каждой единице перевода, в которой она используется.

Вдоль строк, которые я читаю: чистая виртуальная функция не используется. ODR применяется только к используемым функциям. Разве это не означает, что следующее будет законным? Я предполагаю, что ответ отрицательный, но тогда я не могу понять почему.

//x.h
struct A
{
   virtual void f() = 0;
};

//y.cpp
#include "x.h"
void A::f()
{
}

//z.cpp
#include "x.h"
#include <iostream>
void A::f()
{
   std::cout << "Hello" << std::endl;
}

//main.cpp
#include "x.h"
struct B:A
{
   virtual void f()
   {
      A::f();
   }
};

int main()
{
   A* p = new B;
   p->f();
}

person Armen Tsirunyan    schedule 10.11.2010    source источник
comment
Это компилируется / связывается? Я чувствую, что возникнет ошибка ссылки из-за реализации дублирующей структуры A.   -  person Stephane Rolland    schedule 10.11.2010
comment
@Rolland: что ты имеешь в виду? Определение класса может и должно появиться в каждой единице перевода, в которой используется класс ...   -  person Armen Tsirunyan    schedule 10.11.2010
comment
@Armen, ладно, оно может появляться везде, но не несколько раз. В примере есть ДВЕ реализации A :: f (); в y.cpp и в z.cpp   -  person Stephane Rolland    schedule 10.11.2010
comment
Кажется, вы действительно нашли дыру в стандарте: стандарт не упоминает, когда и когда используются чистые виртуальные функции.   -  person Philipp    schedule 10.11.2010
comment
@Stephane Rolland: Прочтите стандарт еще раз: каждая программа должна содержать ровно одно определение каждой не встроенной функции [...], которая используется в этой программе, и виртуальная функция-член используется, если она не чистая. Это означает, что если виртуальная функция-член является чистой, стандарт можно интерпретировать так, чтобы он не использовался, что допускало бы сколь угодно много определений.   -  person Philipp    schedule 10.11.2010
comment
Компоновщик GCC действительно выдает ошибку (даже без main.cpp), но это не ясно из стандарта или даже неверно, в зависимости от значения if.   -  person Philipp    schedule 10.11.2010
comment
Я не понимаю стандарта по-твоему. Какие аргументы заставляют вас заявить: сколь угодно много определений?   -  person Stephane Rolland    schedule 10.11.2010
comment
@Stephanie: Я думаю, мы все согласны, что этот пример противоречит намерениям Стандарта, и это нормально, что он вызывает проблемы. Обсуждается вопрос, не забыли ли случайно формулировки Стандарта охватить этот случай.   -  person aschepler    schedule 10.11.2010
comment
Хорошо, а что это за код Армена? struct A { virtual void f() = 0; }; //y.cpp #include "x.h" void A::f() { } Он реализует чисто виртуальную функцию. Хуже всего: он делает это дважды.   -  person Stephane Rolland    schedule 10.11.2010
comment
Вы все можете согласиться со словами и понятиями, которые вам не очень хорошо понятны. (ну это то, что я понимаю на своем уровне :-))   -  person Stephane Rolland    schedule 10.11.2010
comment
@Stephane Rolland: Хотя предоставление двух определений для любой не встроенной функции неверно, что плохого в реализации чистой виртуальной функции?   -  person CB Bailey    schedule 10.11.2010
comment
@ Чарльз, возможно, именно в этом я ограничен. Для меня (и я не являюсь Стандартом, ладно, не поймите меня таким образом ;-)) чистая функция-член не должна иметь никакой реализации, поскольку она чистая виртуальная.   -  person Stephane Rolland    schedule 10.11.2010
comment
@ Стефан Роллан: Я думаю, вы неправильно поняли, что такое чистый виртуальный. Все это означает, что вы не можете создать экземпляр класса, который не имеет нечистого окончательного переопределителя для каждой функции, которая является чисто виртуальной в любом прямом или косвенном базовом классе. Чистая виртуальная функция не обязательно должна иметь реализацию, если она не используется, но может иметь ее - и должна иметь ее, если используется.   -  person CB Bailey    schedule 10.11.2010
comment
@ Стефан. На самом деле, дать определение PVF - это нормально. И для этого есть веские причины. см., например, stackoverflow.com/questions/2089083/   -  person Armen Tsirunyan    schedule 10.11.2010
comment
@ Чарльз и @ Армен. Хорошо, я должен задуматься об этой ссылке. Спасибо за ссылку. На мой взгляд, чистое виртуальное и абстрактное были так близки ... Извините за беспокойство. Я вернусь, если мне будет что добавить ;-)   -  person Stephane Rolland    schedule 10.11.2010
comment
Может быть, это не то место, где мне отвечать, может быть, просто ссылка, но зачем объявлять функцию-член чистой виртуальной какой-либо смыслом, если она должна дать ей реализацию? ? ? Что это приносит?   -  person Stephane Rolland    schedule 10.11.2010
comment
@Stephane: Например, вы хотите сделать свой класс абстрактным, но в нем нет чистых виртуальных функций. Что вы делаете? Хорошо, вы делаете деструктор чисто виртуальным. Но деструктор ДОЛЖЕН иметь определение. Другой сценарий. Ваш метод - единственный виртуальный метод, и он чисто виртуальный. Но было бы неплохо предоставить реализацию по умолчанию, которую подклассы могут вызывать среди дополнительных действий.   -  person Armen Tsirunyan    schedule 10.11.2010
comment
вау, я полностью проиграл ... Я никогда не видел чистого виртуального деструктора.   -  person Stephane Rolland    schedule 10.11.2010


Ответы (5)


Эти два пункта не исключают друг друга. То, что виртуальная функция используется, если она не чистая, не означает, что верно обратное. Если виртуальная функция является чистой, это не означает, что она не используется. Его все еще можно использовать, «если его имя появляется в потенциально оцениваемом выражении», например, в вашем примере: A::f();.

person CB Bailey    schedule 10.11.2010
comment
Эм ... Я все еще не уверен. Используемый термин определяется через понятие потенциально оцениваемого выражения, и есть исключение для чистых виртуальных функций. Таким образом, A :: f () является таким же потенциально вычисляемым выражением, как и x- ›f (); но последнее не означает, что функция используется ... - person Armen Tsirunyan; 10.11.2010
comment
Давайте посмотрим на цитату @Steve M: чистую виртуальную функцию нужно определять только в том случае, если она вызывается с синтаксисом квалифицированного идентификатора (5.1) или как если бы с (12.4). Если бы они добавили. В этом случае говорят, что используется чистая виртуальная функция, все будет хорошо. Но они этого не сделали :) - person Armen Tsirunyan; 10.11.2010
comment
3.2p2 не имеет смысла, если его не читать так, как если бы соблюдение каких-либо индивидуальных требований заставляло использовать символ. Например, функция распределения используется новым выражением. Но он, безусловно, также используется, если явно вызван с использованием operator new. - person aschepler; 10.11.2010
comment
@ Армен Цирунян: Прочтите статью еще раз внимательно. Для чисто виртуальных функций нет исключения; исключение составляют виртуальные функции, не являющиеся чистыми, которые автоматически используются независимо от того, используются ли они иным образом. - person CB Bailey; 10.11.2010
comment
@Charles: Вы имеете в виду, что если есть виртуальная функция, не являющаяся чистой, она ДОЛЖНА быть определена, вызывается она, скажем, откуда угодно? Ах да, в этом есть смысл ... - person Armen Tsirunyan; 10.11.2010
comment
@ Армен Цирунян: Да. На это есть практическая причина: во многих реализациях C ++ адрес такой функции должен быть помещен в vtable любого объекта, производного от класса, который не отменяет функцию. Такой класс может быть определен только в другой единице трансляции, скомпилированной позже, поэтому для реализаций проще всего, если такая функция будет считаться используемой во всех случаях, поэтому реализация может зависеть от возможности найти функцию. во время ссылки. Чистые виртуальные функции должны быть переопределены в любом инстанцируемом классе, поэтому их это не касается. - person CB Bailey; 10.11.2010
comment
@Armen возражает против p->f(). Это фактически сделало бы использование чистой виртуальной функции, но, конечно, не предназначено для этого. Однако отчет о проблеме уже есть на open-std. org / jtc1 / sc22 / wg21 / docs / cwg_active.html # 1174 об этом. - person Johannes Schaub - litb; 28.11.2010
comment
@litb: Я сознательно избегал каких-либо суждений о том, использует ли x->f() чистую виртуальную функцию; Мне не нужно было отвечать на исходный вопрос. ;) - person CB Bailey; 28.11.2010

Этот код нарушает ODR. A :: f определяется многократно. Следовательно, у него есть УБ.

Использование нескольких определений в единицах перевода разрешено только для следующих слов по цене $ 3,2 / 5.

Может быть несколько определений типа класса (пункт 9), типа перечисления (7.2), встроенной функции с внешней связью (7.1.2), шаблона класса (пункт 14), шаблона нестатической функции (14.5.5). , статический член данных шаблона класса (14.5.1.3), функция-член шаблона класса (14.5.1.1) или специализация шаблона, для которой некоторые параметры шаблона не указаны (14.7, 14.5.4) в программе при условии, что каждый определение появляется в другой единице перевода и при условии, что определения удовлетворяют следующим требованиям.

person Chubsdad    schedule 10.11.2010

Как отметил @Charles Bailey, ваш A::f на самом деле используется, хотя он чисто виртуальный. Но это не главное.

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

3.2p1 Ни одна единица перевода не должна содержать более одного определения любой переменной, функции, типа класса, типа перечисления или шаблона.

3.2p3 Каждая программа должна содержать ровно одно определение каждой не встроенной функции или объекта, которые используются в этой программе; диагностика не требуется.

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

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

// x.cpp
void f() {}
void g() {}

// y.cpp
#include <iostream>
void f() {
  std::cout << "Huh" << std::endl;
}
void h() {}

// z.cpp
void g();
void h();
int main() { 
  g();
  h();
  return 0;
}
person aschepler    schedule 10.11.2010
comment
Нет, погодите, 3.2p1 действительно говорит о единице перевода. Я думаю, что на самом деле здесь может быть фразировочная дыра. - person aschepler; 10.11.2010
comment
3.2 / 1 - это не дыра для фраз. Он применяется к встроенным функциям, классам и перечислениям, которые разрешено определять один раз на единицу перевода, в отличие от не встроенных функций и объектов. - person CB Bailey; 10.11.2010
comment
@ Чарльз: Согласен. Но какое требование говорит о том, что моя программа-пример плохо сформирована? Пункт 1 не применяется, поскольку определения даны в разных единицах перевода. P2 не говорит, что f() используется. P3 не применяется, потому что он не используется. P4 посвящен определениям классов. P5 касается вещей, в том числе встроенных функций, которые действительно можно многократно определять. - person aschepler; 10.11.2010
comment
Я думаю, что это предполагается равным 3,2 / 5, что точно описывает, какие объекты могут иметь более одного определения в программе и ограничения на несколько определений. Я признаю, что это только косвенно, если сущность отсутствует в списке, она не может иметь более одного определения в программе (т.е. только ноль или одно определение). - person CB Bailey; 10.11.2010
comment
Некоторое время назад я разместил это на usenet, и оказалось, что это дефект. Но я не отправлял его в комитет как отчет о проблеме. Вы можете захотеть это сделать. - person Johannes Schaub - litb; 28.11.2010

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

person Yttrill    schedule 28.11.2010
comment
12.4p7: деструктор может быть объявлен virtual (10.3) или чистый virtual (10.4); если в программе создаются какие-либо объекты этого класса или любого производного класса, деструктор должен быть определен. (Мое примечание: «объекты» включают подобъекты-члены.) - person aschepler; 29.11.2010

[class.abstract]: «Чистую виртуальную функцию нужно определять только в том случае, если она вызывается с синтаксисом квалифицированного идентификатора (5.1) или как если бы с (12.4)».

Ваш A::f вызывается B::f, поэтому должно быть одно определение A::f.

person Steve M    schedule 10.11.2010
comment
Я согласен, что согласно вашей цитате должно быть определение для A :: f. Но я не понимаю, какой пункт требует наличия единственного определения. - person Armen Tsirunyan; 10.11.2010
comment
@ Армен Цирунян: См. Мой ответ для вашего разъяснения - person Chubsdad; 10.11.2010