Здравствуйте все!
При попытке скомпилировать под 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[6]: Перегрузка методов базовых классов при множественном наследовании (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[6]: Перегрузка методов базовых классов при множественном наследовании (msvc)
Здравствуйте, B0FEE664, Вы писали:
BFE>Вот вам как в Java, наследование интерфейсов и реализация:
<>
А вот зачем в интерфейсах объявлен и тривиально определён публичный виртуальный деструктор?
Это что, приглашение убивать объект через любую его рукоятку?
Перекуём баги на фичи!
Re[8]: Перегрузка методов базовых классов при множественном наследовании (msvc)
Здравствуйте, B0FEE664, Вы писали:
К>>А вот зачем в интерфейсах объявлен и тривиально определён публичный виртуальный деструктор? К>>Это что, приглашение убивать объект через любую его рукоятку?
BFE>да
А не страшно?
Обычно люди как-то жёстче относятся к политикам владения.
Либо — кто создал конкретный объект (и, соответственно, знает его финальный тип), тот его и убивает, а всем остальным даёт интерфейсы объекта попользоваться на время.
Либо это разделяемое владение, неинтрузивное на shared_ptr (см. выше про финальный тип) или интрузивное на методах addref/release, которые объявлены в интерфейсах, но определены в финальном типе.
Либо, на крайний случай, это эстафета: фабрика создаёт объект и отдаёт его интерфейс, в котором есть ручка для убивания. Но это не очень вяжется с множественностью интерфейсов...
Перекуём баги на фичи!
Re: Перегрузка методов базовых классов при множественном наследовании (msvc)
VEC>Есть два базовых класса, методы которых я хочу явно перекрыть в классе-наследнике (различное поведение) + реализовать третье поведение, если вызван метод наследника без явного указания базового интерфейса.
Дружище, я не понял твой код, потому что там объявлены виртуальными функции, которые при таком наследовании никогда не будут перегружены. Соответственно, я плохо понимаю, чего ты хочешь. Поэтому позволь тебе задать уточняющий вопрос в виде кода
Код:
class DbgInher1 {
public:
virtual void F() {
printf("\ninherit 1!!!");
}
};
class DbgInher2 {
public:
virtual void F() {
printf("\ninherit 2!!!");
}
};
class DbgInherM : public DbgInher1, public DbgInher2 {
public:
virtual void F() {
printf("\ninherit M!!!");
}
};
int main(int argc, char *argv[])
{
DbgInherM a;
a.DbgInher1::F();
a.DbgInher2::F();
a.DbgInherM::F();
a.F();
printf("\n\n\n");
}
При этом если убрать все слова virtual, то поведение кода не изменится, т.к. все три функции ни разу не виртуальны по сути.
Мне показалось, что именно предположение о том, что функции должны перегружатся, запутало тебя.
Правильно ли я понял, что это именно то поведение, которое ты хотел?
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() — по чтению.
Re[2]: Перегрузка методов базовых классов при множественном наследовании (msvc)
Здравствуйте, VladEC, Вы писали:
VEC>Да, на таком варианте и остановились. VEC>Изначально не хотели делать "масло масляное", т.к. очевидно, что IWriter::SetPosition() управляет курсором по записи, а IReader::SetPosition() — по чтению.
Можно сделать набор перегруженных функций
void SetPosition( IReader*, int newPos );
void SetPosition( IWriter*, int newPos );
void SetPosition( IStorage*, int newPos );
в которых уже нужные методы и звать, а в коде использовать толкьо редиректоры.
Методы, кстати, можно сделать приватными, а редиректоры -- друзьямИ, но я бы такой ерундой страдать не стал.
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[6]: Перегрузка методов базовых классов при множественном наследовании (msvc)
Здравствуйте, B0FEE664, Вы писали:
BFE>Вот вам как в Java, наследование интерфейсов и реализация: BFE>А вообще, я плохо понял, что вам надо.
В данном конкретном случае хотелось иметь возможность раздельного управления курсорами через соответствующие интерфейсы: IReader::SetPosition() и IWriter::SetPosition() (both bidirectional & unidirectional storages), т.е. по необходимости предоставлять раздельные реализации методов с совпадающими сигнатурами, но принадлежащих различным интерфейсам.
Соответственно, рассчитывали на то, что наследование различных интерфейсов предполагает (допускает) различные реализации методов (не все стандарт, как отче наш знают, да).
Уже понятно стало, что без бубна (aka прокси или перенаправления вызовов) в обычном C++ так сделать нельзя, т.ч. вопрос закрыт.
Если бы использовался только MSVC, можно было использовать ключевое слово __interface и радоваться, но нужен "глубоко" портируемый код.
Re[7]: Перегрузка методов базовых классов при множественном наследовании (msvc)
Здравствуйте, Кодт, Вы писали:
К>А вот зачем в интерфейсах объявлен и тривиально определён публичный виртуальный деструктор? К>Это что, приглашение убивать объект через любую его рукоятку?
да
И каждый день — без права на ошибку...
Re[7]: Перегрузка методов базовых классов при множественном наследовании (msvc)
К>А вот зачем в интерфейсах объявлен и тривиально определён публичный виртуальный деструктор? К>Это что, приглашение убивать объект через любую его рукоятку?
Грубо говоря, да.
Объяснять долго, но так надо
Re[7]: Перегрузка методов базовых классов при множественном наследовании (msvc)
Здравствуйте, Кодт, Вы писали: VEC>>Команда в отпусках почти вся, т.ч. отходить от Coding Conventions в одно лицо нельзя, придется переименовывать. К>Вот радости-то будет вернувшимся из отпуска — интерфейсы изменились, зато конвенции соблюдены
В данном случае все правильно.
Единый стиль стоит выше имен отдельных методов. Согласовано с оставшимися в строю.
Re[8]: Перегрузка методов базовых классов при множественном наследовании (msvc)
Здравствуйте, Erop, Вы писали:
VEC>>Да, на таком варианте и остановились. VEC>>Изначально не хотели делать "масло масляное", т.к. очевидно, что IWriter::SetPosition() управляет курсором по записи, а IReader::SetPosition() — по чтению. E>Можно сделать набор перегруженных функций
void SetPosition( IReader*, int newPos );
E>void SetPosition( IWriter*, int newPos );
E>void SetPosition( IStorage*, int newPos );
в которых уже нужные методы и звать, а в коде использовать толкьо редиректоры. E>Методы, кстати, можно сделать приватными, а редиректоры -- друзьямИ, но я бы такой ерундой страдать не стал.
Да много чего мжно сделать. Но это все выходит за рамки java-style с мульти-интефрейсами, который хотелось использовать изначально.
Т.ч. просто решили не заводить "пересекающихся" имен тем более, что владельцы кода — мы.
К чужому коду в подобной ситуации придется прикручивать костыли, т.ч. все это обсуждение в любом случае не канет в Лету.
Re[7]: Перегрузка методов базовых классов при множественном наследовании (msvc)
Здравствуйте, VladEC, Вы писали:
VEC>В данном конкретном случае хотелось иметь возможность раздельного управления курсорами через соответствующие интерфейсы: IReader::SetPosition() и IWriter::SetPosition() (both bidirectional & unidirectional storages), т.е. по необходимости предоставлять раздельные реализации методов с совпадающими сигнатурами, но принадлежащих различным интерфейсам.
Раздельные реализации предполагают раздельные типы при сохранении одних и тех же сигнатур.
VEC>Соответственно, рассчитывали на то, что наследование различных интерфейсов предполагает (допускает) различные реализации методов (не все стандарт, как отче наш знают, да). VEC>Уже понятно стало, что без бубна (aka прокси или перенаправления вызовов) в обычном C++ так сделать нельзя, т.ч. вопрос закрыт. VEC>Если бы использовался только MSVC, можно было использовать ключевое слово __interface и радоваться, но нужен "глубоко" портируемый код.
не вижу в этом ничего невозможного:
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() {}
};
class IWriterImpl : public IWriter
{
public:
virtual int Write(const void* buf, size_t& size);
virtual int SetPosition(size_t pos);
virtual ~IWriterImpl() {}
//make it private:int m_pos;
};
int IWriterImpl::Write(const void* buf, size_t& size) { return 0;}
int IWriterImpl::SetPosition(size_t pos) { m_pos = (int)pos; return m_pos;}
class IReaderImpl : public IReader
{
public:
virtual int Read(void* buf, size_t& size);
virtual int SetPosition(size_t pos);
virtual ~IReaderImpl() {}
//make it private:int m_pos;
};
int IReaderImpl::Read(void* buf, size_t& size) { return 0;}
int IReaderImpl::SetPosition(size_t pos) { m_pos = (int)pos; return m_pos;}
class IMyBiStorageImpl: public IReaderImpl, public IWriterImpl
{
public:
virtual ~IMyBiStorageImpl() {}
};
class IMyUniStorageImpl: 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);
virtual ~IMyUniStorageImpl() {}
//make it private:int m_pos;
};
int IMyUniStorageImpl::Read(void* buf, size_t& size)
{
return 3;
}
int IMyUniStorageImpl::Write(const void* buf, size_t& size)
{
return 2;
}
int IMyUniStorageImpl::SetPosition(size_t pos)
{
m_pos = pos; return m_pos;
}
int main()
{
IMyUniStorageImpl sUni;
IWriter* wUni = &sUni;
IReader* rUni = &sUni;
wUni->SetPosition(1);
wUni->SetPosition(2);
IMyBiStorageImpl sBi;
IWriter* wBi = &sBi;
IReader* rBi = &sBi;
wBi->SetPosition(3);
wBi->SetPosition(4);
return 0;
}
Или вам ещё нужен IStorage?
И каждый день — без права на ошибку...
Re[9]: Перегрузка методов базовых классов при множественном наследовании (msvc)
Здравствуйте, Кодт, Вы писали:
К>>>А вот зачем в интерфейсах объявлен и тривиально определён публичный виртуальный деструктор? К>>>Это что, приглашение убивать объект через любую его рукоятку? BFE>>да К>А не страшно?
Это было сделано, чтобы показать на необходимость виртуализации деструкторов, которая у VladEC в исходном посте отсутствует. И только.
И каждый день — без права на ошибку...
Re[10]: Перегрузка методов базовых классов при множественном наследовании (msvc)
Здравствуйте, B0FEE664, Вы писали:
К>>>>А вот зачем в интерфейсах объявлен и тривиально определён публичный виртуальный деструктор? К>>>>Это что, приглашение убивать объект через любую его рукоятку? BFE>>>да К>>А не страшно? BFE>Это было сделано, чтобы показать на необходимость виртуализации деструкторов
В том то и дело, что такой необходимости нет
А есть выбор — virtual или protected. Делая такой выбор программист сообщает свои намерения.
BFE>которая у VladEC в исходном посте отсутствует. И только.
В исходном сообщении они вообще private
Re[2]: Перегрузка методов базовых классов при множественном наследовании (msvc)
Я не могу понять вот чего (и поэтому не уверен, что понял вопрос правильно): ты оформляешь верхние классы как интерфейсы, но при этом делаешь таким образом, чтобы эти интерфейсы никогда не работали. Т.е. на мой взгляд вообще нет никакого смысла в том, чтобы Storage наследовать от IReader IWriter, не используя виртуального наследования от общего предка. Я бы сделал так:
class ISome {
virtual int SetPosition(size_t pos) = 0;
virtual ~ISome() {}
}
class IReader : public virtual ISome
{
virtual int Read(void* buf, size_t& size) = 0;
virtual ~IReader() {}
}
class IWriter : public virtual ISome
{
virtual int Write(const void* buf, size_t& size) = 0;
virtual ~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);
}
При твоём коде я бы понял, в чём проблема. а так — не понимаю. Есть три разные функции, c общим именем SetPosition, зачем-то объявленные виртуальными. Ты хочешь их вызывать по отдельности — ну так вызывай, кто мешает. В чём вопрос-то?
Re[2]: Перегрузка методов базовых классов при множественном наследовании (msvc)
Здравствуйте, Molchalnik, Вы писали:
M>Дружище, я не понял твой код, потому что там объявлены виртуальными функции, которые при таком наследовании никогда не будут перегружены.
отвечу за топикстартера.
Начнём с того, что вообще забудем про наследование интерфейсов.
Пусть у нас есть некоторый туманный объект, который можно рассматривать как читатель (и применять к нему функции read и seek), а можно как писатель (и, соответственно, применять write и seek).
Так вот, иногда seek читателя и писателя хочется выполняють совместно, а иногда порознь.
Если мы запретим перегрузку имён, то ситуация будет выглядеть так
class Storage
{
private:
void read();
void write();
int seek_reader(int);
int seek_writer(int);
int seek_both(int);
friend class IReader;
friend class IWriter;
friend class IStorage;
};
И теперь начинается самое интересное. Интерфейс, скажем, читателя, может работать так или этак
Ромбовидное наследование — хоть явное, как у тебя, хоть сведённое к перегрузке имён, как у топикстартера, — это всегда "этак", и нужно потратить определённые усилия и использовать нестандартные расширения компиляторов, чтобы сделать "так".
Если же по умолчанию у нас делается "так" (т.е. если мы с самого начала даём разные имена методам интерфейсов), то из "так" сделать "этак" гораздо проще:
class Storage
{
private:
void read();
void write();
int seek_reader(int p) { return seek_both(p); }
int seek_writer(int p) { return seek_both(p); }
int seek_both(int);
friend class IReader;
friend class IWriter;
friend class IStorage;
};
Перекуём баги на фичи!
Re[4]: Перегрузка методов базовых классов при множественном наследовании (msvc)
Пффф, пусть использует паттерн "шаблонный метод" : объявит все функции интерфейсов невиртуальными. Методы публичного интерфейса пусть вызывают виртуальные функции.
O>>Но вообще интересно, это вообще как используют клиентом объекта, как вызываться будет SetPosition для reader и writer — не лучше ли переобозвать методы интерфейса -> SetReaderPosition и SetWriterPosition — в нормальной IDE такой рефакторинг делается 2мя кликами.
VEC>Да, на таком варианте и остановились. VEC>Изначально не хотели делать "масло масляное", т.к. очевидно, что IWriter::SetPosition() управляет курсором по записи, а IReader::SetPosition() — по чтению.
паттерн "Шаблонная функция" спасёт отцов русской демократии, см. мой предыдущий пост
Re[8]: Перегрузка методов базовых классов при множественном наследовании (msvc)
Здравствуйте, Evgeny.Panasyuk, Вы писали: BFE>>Это было сделано, чтобы показать на необходимость виртуализации деструкторов EP>В том то и дело, что такой необходимости нет EP>А есть выбор — virtual или protected. Делая такой выбор программист сообщает свои намерения.
Намерения — да..., а вот практика показывает, что если есть хотя бы одна виртуальная функция, то не имеет смысла не делать деструктор виртуальным.
Если бы в следующем коде деструкторы изначально были бы объявлены виртульными, то ошибки бы не было:
Скрытый текст
class IReader
{
public:
virtual void Release() = 0;
protected:
//virtual
~IReader() {}
};
class IReaderImpl : public IReader
{
public:
IReaderImpl() : m_count(1) {}
virtual void Release()
{
m_count--;
if ( m_count <= 0 )
delete this;
}
protected:
//virtual
~IReaderImpl() {}
private:
int m_count;
};
class IWriter
{
public:
virtual void Release() = 0;
protected:
~IWriter() {}
};
class IWriterImpl : public IWriter
{
public:
IWriterImpl() : m_count(1) {}
virtual void Release()
{
m_count--;
if ( m_count <= 0 )
delete this;
}
protected:
~IWriterImpl() {}
private:
int m_count;
};
class OMyGod : public IReaderImpl, public IWriterImpl
{
public:
virtual void Release()
{
IReaderImpl::Release();
}
protected:
~OMyGod()
{
std::cout << "I expected to die" << std::endl;
}
private:
char m_nom[1000000];
};
void MakeDie(IReader* p)
{
p->Release();
}
int main()
{
for(int i = 0; i < 10; i++)
{
IReader* pOMyGod = new OMyGod();
MakeDie(pOMyGod);
// all right?
}
std::cout << "Press any key to exit..." << std::endl;
char ch = _getch();
return 0;
}
BFE>>которая у VladEC в исходном посте отсутствует. И только. EP>В исходном сообщении они вообще private
как и остальные методы.
И каждый день — без права на ошибку...
Re[9]: Перегрузка методов базовых классов при множественном наследовании (msvc)
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, Molchalnik, Вы писали:
M>>паттерн "Шаблонная функция" спасёт отцов русской демократии, см. мой предыдущий пост
К>Паттерн "переименовать методы" спасёт отцов русской демократии ещё быстрее. К>Шаблонная функция противоречит стилю кодирования в компании автора.
"Заметным ограничением выступает Coding Style/Guidelines, принятый командой, — не определять функций в объявлениях интерфейсов (в заголовках, впрочем, тоже)."
Я когда прочитал эту фразу, не подумал, что следование coding style может доходить до глупости.
Данное правило очень разумно, так как вместе с правилом о том, что функции определяются один и только один раз, в реализации, позволяет отслеживать не переопределённые функции. Так же при множественном наследовании это избавляет от геморроя. Полезная практика.
Но запрещать на этом основании паттерн "шаблонная функция" и невиртуальный интерфейс — это уже перебор, т.к. противоречит целям введения правила, точнее, не способствует им.
Это позор программирования — пусть здравый смысл и целесообразность горит синим пламенем, главное, что я я CodeStyle соблюл