Здравствуйте все!
При попытке скомпилировать под Win32 код, который успешно собирается gcc и clang-ом под Linux, Android, MacOS X и iPhone OS вылезла ошибка, решить которую сходу (красиво!) не удается.
Упростил по максимуму, надеюсь, суть проблемы будет понятна.
Есть два базовых класса, методы которых я хочу явно перекрыть в классе-наследнике (различное поведение) + реализовать третье поведение, если вызван метод наследника без явного указания базового интерфейса. header-file
class IReader
{
virtual int Read(void* buf, size_t& size) = 0;
virtual int SetPosition(size_t pos) = 0;
~IReader() {}
}
class IWriter
{
virtual int Write(const void* buf, size_t& size) = 0;
virtual int SetPosition(size_t pos) = 0;
~IWriter() {}
}
class Storage: public IReader, public IWriter
{
public:
int Read(void* buf, size_t& size);
int Write(const void* buf, size_t& size);
int SetPosition(size_t pos);
}
source-file
int Storage::Read(void* buf, size_t& size)
{return 0;} // here and below are stubs (obviously!)int Storage::Write(const void* buf, size_t& size)
{return 0;}
int Storage::SetPosition(size_t pos)
{return 0;}
int Storage::IReader::SetPosition(size_t pos)
{return -1;}
int Storage::IWriter::SetPosition(size_t pos)
{return -2;}
POSIX-компиляторы кушают такой код спокойно и без предупреждений, cl выдал ошибку C2509.
При этой ошибке рекомендуют:
1) Не страдать фигней и переименовать методы базовых классов (не хочу вносить избыточность в имя метода, который может использоваться безотносительно данного наслединка)
2) Вместо class использовать ключевое слово __interface для базовых классов (не получается: в __interface нельзя объявлять вложенные классы, что мне необходимо)
3) Включить перекрываемые методы как inline, т.е. описать прямо в заголовке наследника. Если длинные — использовать перенаправление на функции с отличающимися именами, определить которые можно уже в cpp-файле.
Третий вариант выглядит жизнеспособно, но применять его не очень хочется. Кому-нибудь удавалось собирать подобный код без плясок с бубном?
Или, может, есть еще варианты подобных перекрытий? NOTE: перефигачить архитектуру не предлагать
Re: Перегрузка методов базовых классов при множественном наследовании (msvc)
Здравствуйте, VladEC, Вы писали:
VEC>Здравствуйте все!
VEC>NOTE: перефигачить архитектуру не предлагать
Как вариант -- выпрямить руки...
Если честно, то я не понял что тебе хотелось выразить. Если оставаться в стандартном С++ без сяких расширений разных, то таки определение виртуальной функции берётся из MDT, даже если виртуальный метод с подходящим прототипом есть в нескольких базах...
То есть твой код не соответствует стандарту С++ и юзает расширение gcc и может ещё чьё-то, а так же дыры в стандарте. Это к вопросу о прямизне рук.
Но, на самом деле даже не особо-то и важно, что там в стандарте написано. Ясно же, что ты юзаешь очень тонкое поведение компилятора. Не можешь ответить за ради чего?
То есть что конкретно ты хотел бы выразить-то?
По поводу не изменения архиитектуры. Что-то же менять всё равно понадобится.
IMHO, проще всего, поменять так. Переименовать все три виртуальные функции, и дополнить классы/интерфейсы NVI.
То есть будет примерно так:
class IReader
{
virtual int Read(void* buf, size_t& size) = 0;
int SetPosition(size_t pos) { return setReadPosition( pos ); }
virtual ~IReader() {}
protected:
virtual int setReadPosition(size_t pos) = 0;
}
class IWriter
{
virtual int Write(const void* buf, size_t& size) = 0;
int SetPosition(size_t pos) { return setWritePosition( pos ); }
virtual ~IWriter() {}
protected:
virtual int setWritePosition(size_t pos) = 0;
}
class Storage: public IReader, public IWriter
{
public:
int Read(void* buf, size_t& size);
int Write(const void* buf, size_t& size);
int SetPosition(size_t pos);
protected:
virtual int setReadPosition(size_t pos);
virtual int setWritePosition(size_t pos);
}
либо просто переименовать функции в райтере и ридере...
В общем, пока не объяснишь, что тебе на самом деле надо, либо кто-то это не протелепает, совета дельного тебе дать будет непросто
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re: Перегрузка методов базовых классов при множественном наследовании (msvc)
Здравствуйте, VladEC, Вы писали:
VEC>NOTE: перефигачить архитектуру не предлагать
А немножко дофигачивать архитектуру можно?
#if your compiler is not C++11 compliant or not VC2008+
#define abstract = 0
#define final
#define override
#endif
struct IReaderDisambiguated : IReader
{
virtual int SetPosition(size_t pos) final { return SetReaderPosition(pos); }
virtual int SetReaderPosition(size_t pos) abstract;
};
struct IWriterDisambiguated : IReader
{
virtual int SetPosition(size_t pos) final { return SetWriterPosition(pos); }
virtual int SetWriterPosition(size_t pos) abstract;
};
class Storage : public IReaderDisambiguated, public IWriterDisambiguated
{
virtual int Read(void* buf, size_t& size) override;
virtual int Write(const void* buf, size_t& size) override;
virtual int SetReaderPosition(size_t pos) override;
virtual int SetWriterPosition(size_t pos) override;
};
Перекуём баги на фичи!
Re: Перегрузка методов базовых классов при множественном наследовании (msvc)
Здравствуйте, VladEC, Вы писали:
VEC>Здравствуйте все! VEC>При попытке скомпилировать под Win32 код, который успешно собирается gcc и clang-ом под Linux, Android, MacOS X и iPhone OS вылезла ошибка, решить которую сходу (красиво!) не удается. VEC>Упростил по максимуму, надеюсь, суть проблемы будет понятна. VEC>Есть два базовых класса, методы которых я хочу явно перекрыть в классе-наследнике (различное поведение) + реализовать третье поведение, если вызван метод наследника без явного указания базового интерфейса. VEC>header-file VEC>
VEC>class IReader
VEC>{
VEC>virtual int Read(void* buf, size_t& size) = 0;
VEC>virtual int SetPosition(size_t pos) = 0;
VEC>~IReader() {}
VEC>}
VEC>class IWriter
VEC>{
VEC>virtual int Write(const void* buf, size_t& size) = 0;
VEC>virtual int SetPosition(size_t pos) = 0;
VEC>~IWriter() {}
VEC>}
struct Storage_IReader : IReader {
inline int SetPosition(size_t pos);
};
struct Storage_IWriter : IWriter{
inline int SetPosition(size_t pos);
};
VEC>class Storage: public Storage_IReader , public Storage_IWriter
VEC>{
VEC>public:
VEC>int Read(void* buf, size_t& size);
VEC>int Write(const void* buf, size_t& size);
VEC>int SetPosition_read(size_t pos);
int SetPosition_write(size_t pos);
VEC>}
int Storage_IReader::SetPosition(size_t pos) { return static_cast<Storage*>(this)->setPosition_read(pos); }
VEC>
Re[2]: Перегрузка методов базовых классов при множественном наследовании (msvc)
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, VladEC, Вы писали:
VEC>>NOTE: перефигачить архитектуру не предлагать
К>А немножко дофигачивать архитектуру можно?
Ну это как бы первый вариант — переименовать методы базовых классов.
Мы сейчас к нему и придем, похоже, потому что gcc 4.7 выдал ошибки линковки, возиться с которыми уже не будем. Вылезло один раз и не на cl, значит, может вылезти еще.
Каждый раз костыли вставлять бессмысленно.
Re[3]: Перегрузка методов базовых классов при множественном наследовании (msvc)
Здравствуйте, VladEC, Вы писали:
К>>А немножко дофигачивать архитектуру можно? VEC>Ну это как бы первый вариант — переименовать методы базовых классов.
Это, как бы, полуторный: интерфейсы оставить как есть, но ввести прослойки.
Перекуём баги на фичи!
Re[2]: Перегрузка методов базовых классов при множественном наследовании (msvc)
Здравствуйте, Erop, Вы писали:
VEC>>NOTE: перефигачить архитектуру не предлагать E>Как вариант -- выпрямить руки...
Ну да, всей команде
E>Если честно, то я не понял что тебе хотелось выразить. Если оставаться в стандартном С++ без сяких расширений разных, то таки определение виртуальной функции берётся из MDT, даже если виртуальный метод с подходящим прототипом есть в нескольких базах...
Есть оъекты, работающие по отдельности с интерфейсами IReader и IWriter. Есть объект с интерфейсом IStorage (и запись, и чтение), который можно передать этим объектам (помимо объектов с интерфейсами IReader/IWriter). Была идея сделать "красиво": при обращении к объекту Storage указанием на конкретный интерфейс (IReader/IWriter) задавать, какую именно позицию меняет вызывающий код (по чтению или записи), или и вовсе перекрыть оба SetPosition в самом Storage, если раздельное управление курсором не поддерживается в данном объекте.
Имя функции SetPosition() при этом не избыточное, то есть мы не говорим, что в объекте Writer оно управляет курсором по записи, в Reader — по чтению.
E>По поводу не изменения архиитектуры. Что-то же менять всё равно понадобится.
На данный момент отказались от "красивой" идеи, пришли к "избыточности имен".
У нас в Coding Conventions ограничение: в заголовках, описывающих интефрейсы, не определять непустые функции (т.е. только деструктор виртуальный вставляется).
Т.ч. Ваше решение красивое (и схоже с 3-м вариантом из моих, где в объявление инлайнятся определения перекрываемых методов), но не подходит.
В любом случае спасибо за внимание и идею!
P.S. Загадочность вопросов прямо пропорциональная кривизне рук, кривизна рук прямо пропорциональна размеру команды и стремлению сделать "красиво"
Re[2]: Перегрузка методов базовых классов при множественном наследовании (msvc)
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, VladEC, Вы писали:
К>>>А немножко дофигачивать архитектуру можно? VEC>>Ну это как бы первый вариант — переименовать методы базовых классов.
К>Это, как бы, полуторный: интерфейсы оставить как есть, но ввести прослойки.
Суть идеи была в том, чтобы в Storage можно было управлять общим курсором через SetPosition, а не SetReadPosition/SetWritePosition.
Признаюсь, не сразу увидел, что Вы предлагаете унаследоваться от IReader/IWriter в промежуточных классах.
Для нас тут одна засада: мы в интерфейсах договорились определений функций не создавать.
Re[3]: Перегрузка методов базовых классов при множественном наследовании (msvc)
Здравствуйте, VladEC, Вы писали:
VEC>Здравствуйте, enji, Вы писали: E>>int Storage_IReader::SetPosition(size_t pos) { return static_cast<Storage*>(this)->setPosition_read(pos); } VEC>>>[/ccode]
VEC>Жуть какая
Чего ж тут такого жуткого? Обычный передаточный метод, после встраивания накладных расходов не будет. VEC>Вариант с инлайном в базовых IReader/IWriter (приведенный выше) мне больше понравился
А что за метод такой и чем он тебе может помочь? Насколько я понимаю, в плюсах нельзя указать, метод какого класса ты перекрываешь, так что если у тебя в базовых классах совпадают названия и сигнатуры виртуальных методов, то решения два — или (как тебе сказал Егор выше) NVI или прокси (у меня — статический, у Кодта — динамический)
Re[4]: Перегрузка методов базовых классов при множественном наследовании (msvc)
Здравствуйте, enji, Вы писали:
E>Здравствуйте, VladEC, Вы писали:
VEC>>Здравствуйте, enji, Вы писали: E>>>int Storage_IReader::SetPosition(size_t pos) { return static_cast<Storage*>(this)->setPosition_read(pos); }
VEC>>Жуть какая E>Чего ж тут такого жуткого? Обычный передаточный метод, после встраивания накладных расходов не будет.
VEC>>Вариант с инлайном в базовых IReader/IWriter (приведенный выше) мне больше понравился E>А что за метод такой и чем он тебе может помочь? Насколько я понимаю, в плюсах нельзя указать, метод какого класса ты перекрываешь, так что если у тебя в базовых классах совпадают названия и сигнатуры виртуальных методов, то решения два — или (как тебе сказал Егор выше) NVI или прокси (у меня — статический, у Кодта — динамический)
Тот, который NVI, удобен. Схожий метод я рассматривал с самого начала под п.3
Нарыл его где-то на просторах IBM или MSDN, не вспомню.
От реализации Кодта принципиальных отличий в Вашем решении не вижу, если честно: и там, и там вводится промежуточный класс (прокси), который перенаправляет вызовы к закрытой/защищенной функции, реализация которой отличается. Не углубляясь в подробности нашего решения, несмотря на годность ответа к заданному вопросу, подобный метод не совсем подходит.
Ничем не хуже выглядит определить в заголовке inline-методы IReader::SetPosition/IWriter::SetPosition, перенаправляющие вызов на конкретные закрытые реализации, в самом Storage (п.3).
Уже нарыли сами тут, что стандарт действительно не поддерживает желаемого изначально извращения, т.ч. пришлось-таки "перефигачивать" немножко.
Заметным ограничением выступает Coding Style/Guidelines, принятый командой, — не определять функций в объявлениях интерфейсов (в заголовках, впрочем, тоже).
Re[5]: Перегрузка методов базовых классов при множественном наследовании (msvc)
Здравствуйте, VladEC, Вы писали:
VEC>Суть идеи была в том, чтобы в Storage можно было управлять общим курсором через SetPosition, а не SetReadPosition/SetWritePosition.
В таком случае надо убрать вредное final из IReaderDisambiguated и IWriterDisambiguated и перекрыть SetPosition(size_t pos) в тех Storage, которые не поддерживают раздельное позиционирование.
VEC>Признаюсь, не сразу увидел, что Вы предлагаете унаследоваться от IReader/IWriter в промежуточных классах. VEC>Для нас тут одна засада: мы в интерфейсах договорились определений функций не создавать.
Мне любопытно: чем вызвано столь странное ограничение?
И каждый день — без права на ошибку...
Re[4]: Перегрузка методов базовых классов при множественном наследовании (msvc)
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, VladEC, Вы писали:
К>>>А немножко дофигачивать архитектуру можно? VEC>>Ну это как бы первый вариант — переименовать методы базовых классов. К>Это, как бы, полуторный: интерфейсы оставить как есть, но ввести прослойки.
Еще раз посмотрел ВНИМАТЕЛЬНО , это даже ближе к 3-му решению из моих: добавить перенаправление вызовов в заголовок.
Только у меня inline-методы предусматривались, а у Вас промежуточные классы.
Т.е. мой вариант был такой (надо было привести, конечно)
class Storage: public IWriter, public IReader
{
public:
inline int IWriter::SetPosition(size_t pos){return SetWritePosition(size_t pos);}
inline int IReader::SetPosition(size_t pos){return SetReadPosition(size_t pos);}
// skippedprotected:
int SetWritePosition(size_t pos);
int SetReadPosition(size_t pos);
}
Оно, насколько мне известно, компилируется и работает.
Кстати, я в процессе упрощения кода для примера не упомянул, что над Storage у нас интерфейс IStorage: public IReader, public IWriter, т.ч. получится крокодил...
Здравствуйте, B0FEE664, Вы писали:
BFE>Здравствуйте, VladEC, Вы писали:
VEC>>Суть идеи была в том, чтобы в Storage можно было управлять общим курсором через SetPosition, а не SetReadPosition/SetWritePosition. BFE>В таком случае надо убрать вредное final из IReaderDisambiguated и IWriterDisambiguated и перекрыть SetPosition(size_t pos) в тех Storage, которые не поддерживают раздельное позиционирование.
Да, варианты есть. Я чуть ниже Вашего комментария привел пример, как обойтись без промежуточных классов.
VEC>>Признаюсь, не сразу увидел, что Вы предлагаете унаследоваться от IReader/IWriter в промежуточных классах. VEC>>Для нас тут одна засада: мы в интерфейсах договорились определений функций не создавать.
BFE>Мне любопытно: чем вызвано столь странное ограничение?
Мне тоже, но не я его придумал
Аргумент, который я слышал: не мешать в кучу объявления и определения, определения всегда ищутся в исходнике (сложно определить однозначно, код какого объема можно объявить в заголовке, а какой код надо помещать в исходник). Ну а интерфейсы вообще должны быть абстрактными без определений (java-style).
Re[7]: Перегрузка методов базовых классов при множественном наследовании (msvc)
Здравствуйте, VladEC, Вы писали:
VEC>Аргумент, который я слышал: не мешать в кучу объявления и определения, определения всегда ищутся в исходнике (сложно определить однозначно, код какого объема можно объявить в заголовке, а какой код надо помещать в исходник). Ну а интерфейсы вообще должны быть абстрактными без определений (java-style).
Так IxxxxDisambiguated — не интерфейсы, а просто абстрактные классы. Для внутреннего использования.
Или у вас иерархия строго двухуровневая: базы — только интерфейсы, реализации — только запаянные?
Другое дело, что в ява-стиле нет множественного наследования...
Перекуём баги на фичи!
Re[8]: Перегрузка методов базовых классов при множественном наследовании (msvc)
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, VladEC, Вы писали:
VEC>>Аргумент, который я слышал: не мешать в кучу объявления и определения, определения всегда ищутся в исходнике (сложно определить однозначно, код какого объема можно объявить в заголовке, а какой код надо помещать в исходник). Ну а интерфейсы вообще должны быть абстрактными без определений (java-style).
К>Так IxxxxDisambiguated — не интерфейсы, а просто абстрактные классы. Для внутреннего использования. К>Или у вас иерархия строго двухуровневая: базы — только интерфейсы, реализации — только запаянные?
Я тут чуть выше схему, которая ближе к реальности, привел, в ней Storage — не класс, а интерфейс (IStorage), соответственно, IxxxDisambiguated начинают "торчать" в интерфейсе.
К>Другое дело, что в ява-стиле нет множественного наследования...
В ява как раз есть множественное наследование интерфейсов, чего нам тут не хватило.
Проблема вылезла из-за того, что в С++ не поддерживается (без бубна) наследование разных интерфейсов, в которых совпадают названия и сигнатуры методов.
Если бы мы не являлись владельцами кода, действительно не обошлось бы без middleware
Re[5]: Перегрузка методов базовых классов при множественном наследовании (msvc)
К>>>>А немножко дофигачивать архитектуру можно? VEC>>>Ну это как бы первый вариант — переименовать методы базовых классов. К>>Это, как бы, полуторный: интерфейсы оставить как есть, но ввести прослойки.
VEC>Еще раз посмотрел ВНИМАТЕЛЬНО , это даже ближе к 3-му решению из моих: добавить перенаправление вызовов в заголовок. VEC>Только у меня inline-методы предусматривались, а у Вас промежуточные классы. VEC>Т.е. мой вариант был такой (надо было привести, конечно) VEC>
VEC>class Storage: public IWriter, public IReader
VEC>{
VEC>public:
VEC> inline int IWriter::SetPosition(size_t pos){return SetWritePosition(size_t pos);}
VEC> inline int IReader::SetPosition(size_t pos){return SetReadPosition(size_t pos);}
VEC>// skipped
VEC>protected:
VEC> int SetWritePosition(size_t pos);
VEC> int SetReadPosition(size_t pos);
VEC>}
VEC>
VEC>Оно, насколько мне известно, компилируется и работает.
Сам себе отвечу...
Проверил все-таки, что пишу
В GCC/CLANG(LLVM) такое не компилируется и не работает. Это был вариант, найденный на StackOverflow для cl.
Так что для сохранения "красивых имен" подходят только прокси-класс или NVI. NVI мне нравится больше, т.к. не плодит сущностей...
Команда в отпусках почти вся, т.ч. отходить от Coding Conventions в одно лицо нельзя, придется переименовывать.
Вообще какая-то загадка. На модельных примерах собираться вся эта ерунда перестала.
Т.ч., видимо, в проекте не только в интерфейсах косяки gcc/clang
Что называется, "найди, почему оно собиралось, когда не должно было". Подозреваю, что до линковки необъявленных методов не доходило — unit test еще не прикрутили даже
Резюме: ВСЕМ помогающим спасибо за умные мысли и дельные советы!
"Минусаторам" вопросов :-P
Попробуйте найти ответ на вопрос "перекрытие методов с совпадающими сигнатурами при множественном невиртуальном наследовании", не погружаясь в стандарт по самые помидоры.
Re[5]: Перегрузка методов базовых классов при множественном наследовании (msvc)
VEC>Кстати, я в процессе упрощения кода для примера не упомянул, что над Storage у нас интерфейс IStorage: public IReader, public IWriter, т.ч. получится крокодил...
IReader IWriter
\ /
IStorage
|
Storage
Вот вам как в Java, наследование интерфейсов и реализация:
class IReader
{
public:
virtual int Read(void* buf, size_t& size) = 0;
virtual int SetPosition(size_t pos) = 0;
virtual ~IReader() {}
};
class IWriter
{
public:
virtual int Write(const void* buf, size_t& size) = 0;
virtual int SetPosition(size_t pos) = 0;
virtual ~IWriter() {}
};
//IMyStorage вместо IStorage, потому, что у майкрософта есть свой IStorage class IMyStorage: public IReader, public IWriter
{
public:
virtual int Read(void* buf, size_t& size) = 0;
virtual int Write(const void* buf, size_t& size) = 0;
virtual int SetPosition(size_t pos) = 0;
virtual ~IMyStorage() {}
};
class Storage: public IMyStorage
{
public:
int Read(void* buf, size_t& size);
int Write(const void* buf, size_t& size);
int SetPosition(size_t pos);
virtual ~Storage() {}
};
int Storage::Read(void* buf, size_t& size)
{
return 3;
}
int Storage::Write(const void* buf, size_t& size)
{
return 2;
}
int Storage::SetPosition(size_t pos)
{
return 1;
}
int main()
{
Storage s;
IWriter* w = &s;
IReader* r = &s;
std::cout << "Press any key to exit..." << std::endl;
char ch = _getch();
return 0;
}
А вообще, я плохо понял, что вам надо.
И каждый день — без права на ошибку...
Re[5]: Перегрузка методов базовых классов при множественном наследовании (msvc)
>> Заметным ограничением выступает Coding Style/Guidelines, принятый командой, — не определять функций в объявлениях интерфейсов (в заголовках, впрочем, тоже).
Понятно, закос под Java делаете — где "множественное наследование" только для интерфейсов.
Но вообще интересно, это вообще как используют клиентом объекта, как вызываться будет SetPosition для reader и writer — не лучше ли переобозвать методы интерфейса -> SetReaderPosition и SetWriterPosition — в нормальной IDE такой рефакторинг делается 2мя кликами.
Re[6]: Перегрузка методов базовых классов при множественном наследовании (msvc)
Здравствуйте, omgOnoz, Вы писали:
>>> Заметным ограничением выступает Coding Style/Guidelines, принятый командой, — не определять функций в объявлениях интерфейсов (в заголовках, впрочем, тоже). O>Понятно, закос под Java делаете — где "множественное наследование" только для интерфейсов.
Ну да, так и задумано.
Если есть необходимость сочетать функциональность нескольких классов, они агрегируются, а не наследуются (паттерны proxy, facade и т.п.).
O>Но вообще интересно, это вообще как используют клиентом объекта, как вызываться будет SetPosition для reader и writer — не лучше ли переобозвать методы интерфейса -> SetReaderPosition и SetWriterPosition — в нормальной IDE такой рефакторинг делается 2мя кликами.
Да, на таком варианте и остановились.
Изначально не хотели делать "масло масляное", т.к. очевидно, что IWriter::SetPosition() управляет курсором по записи, а IReader::SetPosition() — по чтению.