Здравствуйте, Sm0ke, Вы писали:
S>Есть ещё виртуальные методы. При оверрайде их в классах наследниках приходится повторно прописывать те же аргументы по несколько раз. И при изменении в базовом классе тянуть все изменения везде. Более того это не обязательно должны быть именно виртуальные методы. В моём одном проекте есть набор классов, имеющих набор статических методов с одинаковым именем и прототипом. В этом случае приходится пользоваться поиском-заменой — и я считаю это уже "гемор".
Откройте для себя IDE и инструменты рефакторинга.
Скажем Visual Studio, Resharper C++, CLion, и т.д.
Одним щелчком меняются сигнатуры всех наследников.
Здравствуйте, Marty, Вы писали:
MA>>>Открываешь файл — там класс на 1K+ строчек, и понять, что вообще там за методы или свойства есть — никакого способа нет, кроме как исследовать код через IDE (при чём во всех это сделано через неудобно).
У C++ практически та же проблема при большом количестве inline методов, определённых внутри класса.
Выносить эти определения отдельно... можно, конечно, поставить такое стилевое условие, но может быть жестковато.
SP>>в студии Ctrl M + O Collapse To Definitions. Получаем просто h файл.
M>Когда ты зашел по SSH и запустил 'nano'
nano сношу везде, где всерьёз работаю, и ставлю joe. А у него уже есть Ctrl+G для поиска парной скобки.
Ну и разумеется vim ('%' для того же действия).
Это уже кое-что — хотя придётся попрыгать по всем методам.
Вообще для решения таких задач без IDE придуманы всякие ctags, etags... они делают список, который можно просто просмотреть глазами, а можно в редакторе подключать и использовать для позиционирования на нужную функцию (метод). Внутрь классов и неймспейсов они давно научились заглядывать.
Здравствуйте, Mystic Artifact, Вы писали:
MA> Публичный контракт из C# или Java получить совершенно точно не представляется возможным, пока вы не выполните работу компилятора.
Хм, как правило в этих обоих языках _публичный_ контракт выделяют в виде сущности типа interface. По крайней мере так ну очень рекомендуется. А уже отнаследоваться от интерфейса — сделать реализацию — можно и только на нужные методы.
Конечно, никто не заставляет так делать, и внутри пакетов на это поплёвывают.
(А ведь интерфейсы, в этом смысле, и есть аналоги заголовочников C/C++.)
MA> В реальности — в проектах — есть одна директория куда навалено по заветам FDG — т.е. все в одну кучу,
Реально есть такой завет?
MA> и если вы это видите все в первый десяток раз — то никакие средства IDE — не помогут, не говоря уже о том, что IDE нужна когда нужна но не более. А если код логически расформирован — то внезапно уже и IDE не нужна что бы понять что и где.
Ну при определённом уровне сложности с IDE таки в разы легче
MA> А если есть публичные хидеры — то вообще знакомство происходит не с основания горы с миллионом незначащих подробностей — а с вершины.
MA> Я собственно говоря, не топлю ни за один ни за другой способ. Просто вижу преимущества и первого и второго. Преимущества второго (наличия хидеров) для меня открылись только в последние <10 лет. А 20 лет назад я был восхищен тем, что в C# нп нужно писать эти дурацкие хидеры.
Здравствуйте, Marty, Вы писали:
M>Но вообще — какие-то через-чур наивные вопросы, от человека, который сто лет тут обитает и пишет на плюсах
Можно испускать негатив в адрес Страуструпа, Степанова, Александреску, всего сообщества крестовиков в целом, но ты его направляешь именно на конкретных людей, причём без предварительного наезда с их стороны.
M>По любому, один из классов будет только ссылаться на другой, ссылка или указатель — это уже детали. Более того, я чего-то сомневаюсь, что в каком-либо языке такое вообще возможно
Ты себя крутым считаешь, всех хаишь — сам бред полный написал. Такое невозможно скомпилировать, так возникает бесконечная вложенность в данных.
Я вот что имеел ввиду:
class B;
class A {
public:
void DoSomething() {
m_v = B::GetV();
}
private:
int m_v;
};
class B {
public:
static int GetV() { return 1; }
void DoSomething() {
A a; a.DoSomething();
}
};
Вот такой код не получается скомпилировать, если не выносить функции в другой файл. На строку "m_v = B::GetV()" gcc ругается.
Хотя я не понимают, почему gcc не хочет в 2 этапа это сделать:
1) сначала пробежаться и составить объявления всех классов и функций
2) скомпилировать функции
class B;
class A {
public:
void DoSomething() {
m_v = B::GetV();
}
private:
int m_v;
};
class B {
public:
static int GetV() { return 1; }
void Do() {
A a; a.DoSomething();
}
};
Здравствуйте, maks1180, Вы писали:
M>>По любому, один из классов будет только ссылаться на другой, ссылка или указатель — это уже детали. Более того, я чего-то сомневаюсь, что в каком-либо языке такое вообще возможно
M>Ты себя крутым считаешь, всех хаишь — сам бред полный написал. Такое невозможно скомпилировать, так возникает бесконечная вложенность в данных.
Ты еще и читать не умеешь
M>Я вот что имеел ввиду:
M>
M>class B;
M>class A {
M>public:
M> void DoSomething() {
M> m_v = B::GetV();
M> }
M>private:
M> int m_v;
M>};
M>class B {
M>public:
M> static int GetV() { return 1; }
M> void DoSomething() {
M> A a; a.DoSomething();
M> }
M>};
M>
И в чем проблема? Описал отдельно классы, сделал реализацию. Тут вообще нет никаких проблем
Здравствуйте, maks1180, Вы писали:
m> V>Вообще это плюс. В хидере у тебя интерфейс, в сишнике имплементация.
m> Да, только все современные языки такие как C#, Java ушли от этого "так называемого плюса".
Здравствуйте, Marty, Вы писали:
M>И в чем проблема? Описал отдельно классы, сделал реализацию. Тут вообще нет никаких проблем
Ну вот вынесеш ты тело метода отдельно — будет одна проблема читать (объявление отдельно, определение отдельно, причём в этом примере после объявлений обоих классов A и B).
Нет — будет другая (слишком много определений в основном предложении class).
Что так плохо, что так плохо.
Потому и говорят, что сворачивание в IDE тут самое полезное. Но и в нём нужны переходы из объявления к телу и наоборот.
Здравствуйте, maks1180, Вы писали:
M>Вот такой код не получается скомпилировать, если не выносить функции в другой файл. На строку "m_v = B::GetV()" gcc ругается. M>Хотя я не понимают, почему gcc не хочет в 2 этапа это сделать: M>1) сначала пробежаться и составить объявления всех классов и функций M>2) скомпилировать функции
a)
class B;
class A {
public:
void DoSomething();
private:
int m_v;
};
class B {
public:
static int GetV() { return 1; }
void Do() {
A a; a.DoSomething();
}
};
void A::DoSomething() {
m_v = B::GetV();
}
b)
struct B0 {
static int GetV() { return 1; }
};
struct A {
void DoSomething() {
m_v = B0::GetV();
}
private:
int m_v;
};
struct B : B0 {
void Do() {
A a; a.DoSomething();
}
};
M>Может я что-то не понимаю ?
Возможно стоит вопрос сформулировать точнее. Что именно вас не устраивает?
Здравствуйте, maks1180, Вы писали:
M>Вот такой код не получается скомпилировать, если не выносить функции в другой файл. На строку "m_v = B::GetV()" gcc ругается. M>Хотя я не понимают, почему gcc не хочет в 2 этапа это сделать: M>1) сначала пробежаться и составить объявления всех классов и функций M>2) скомпилировать функции
M>Может я что-то не понимаю ?
Ты можешь сделать метод inline'ом. Для этого не обязательно помещает его реализацию внутрь класса.
M>Да, только все современные языки такие как C#, Java ушли от этого "так называемого плюса".
Ты еще про современный жабаскрипт забыл и еще кучу модного смузихлебного.
В отличие от всего этого модного С и С++ хотя бы логичны и последовательны.
Здравствуйте, maks1180, Вы писали:
M>Для меня основной минус с++ от которого хотелось бы избавиться — это дублирование кода. M>Приходиться одно и тоже писать (название функций) в h и в cpp файлах. И менять тоже нужно в двух местах.
Проблема C++ в том, что да, если кто-то планирует выделять память для класса, то
должен видеть полное его определение. Вот это серьёзная проблема.
Ещё в C++ трудно, неудобно делать PIMPL-идиому, т.к. либо получается фабрика классов,
либо все потроха класса и, главное, все зависимости выезжают в заголовочный файл.
M>1) я пробовал писать всё в h файле (т.е. и тело функций), но тогда возникают проблемы: M>1.1 — если класс A использует класс B и класс B использует класс A — что очень часто бывает в больших проектах. Тогда приходится некоторые функции выносить в cpp.
Не нужно. Нужно сделать forward declaration для класса и лучше вообще
писать код так, что не A использует B, а что A использует некую абстракцию
которой в итоге удовлетворяет B, но A про B ничего непосредственно не знает,
и наоборот, B про A, а только про необходимую абстракцию. Тогда абстракции
могут быть предварительно декларированы и могут быть начаты использованы
без знания о самих A или B.
Абстракция в коде может быть воплощена либо в абстрактном базовом классе,
либо в шаблоне. В большинстве случаев более рационален подход с шаблоном, так
как даёт связывание/диспетчеризацию в момент компиляции, а не в момент исполнения.
Кроме того, в момент разворачивания шаблона он обладает полнотой знания о переданном
ему классе-параметре, что тоже может использовать и что не доступно в варианте
с наследованием.
Главное при работе с C++ -- это изменить своё мышление. C++ имеет другие методы
решения задач, чем языки вроде Java, Delphi, C#. Методы не существующие в этих языках.
А подходы применимые в других языках хоть в C++ и работают, но выглядят часто хуже.
И не нужно программу на C++ превращать в программу на FORTRAN, где всё впихнуто
в один мега-класс зависящий абсолютно от всего чего только возможно. Не нужно
стесняться разделять логику на множество простых абстракций с простыми и минимальными
зависисмостями. Такие абстракции просты, легко проверяемы, в них трудно допустить ошибку.
Их можно отдельно и независимо протестировать. А мегакласс -- это типичный спагетти-код
со всеми его "преимуществами".
При работе с шаблонами нужно не забывать, что не обязательно всё должно попасть в
заголовочмный файл, иногда можно пользоваться extern template.
M>1.2 — проблемы с #define, т.е. если #define определён в cpp после #include, он действует только в одном cpp (обычно один файл cpp = один класс). Если определить #define в h, он будет действовать на все классы которые включены после данного файла.
Честно говоря, пять раз перечитал, но не понял. C-препроцессор во-первых в современном
C++ практически не стоит использовать вообще, всё то же самое, только лучше делается
через декларативное программирование в пространстве типов, с помощью шаблонов.
Во-вторых C-препроцессор это по сути императивный язык программирования, и конечно
там порядок определений важен. Так и должно быть.
M>Может быть данные проблемы уже как-то решены, но я об этом не знаю ?
Да нет на самом деле никакой проблемы...
M>Если нет, может сделать предкомпилятор, который из одного файла (где объявление и реализация) создаст 2 файла h+cpp, которые будут компилироваться.
Модули в C++20, но вряд ли оно сильно чем-то поможет.
Здравствуйте, maks1180, Вы писали:
M>>Современные IDE умеют одним кликом мышки заводить новые классы, добавлять новые функции и члены класса сразу и в хедерах и в исходниках. Так же переименовывать сразу во всех исходниках (у них это в разделе "Рефакторинг" в выпадающем меню)
M>Это да: M>1) часто на сервере нужно править исходники. Там только текстовый терминал.
Это ж замечательно, что там текстовый терминал. Значи там работает лучший
в мире редактор Vim, в котором быстро всё можно сделать. Ну или Emacs если
кому-то Vim претит.
Что было б лучше, если б там был только графический терминал и запускалась
студия? Ужас нахер, в страшном сне только такое приснится.
M>2) современные IDE, часто имеет проблемы если использовать макросы или шаблоны.
Это проблема "современынх IDE", а не макросов или шаблонов. В Vim никаких с этим проблем.
M>4) И намного удобнее когда 1 файл = 1 класс.
Когда есть много классов на пять строчек -- уже неудобно. Тогда лучше файл
как-то по имени подсистемы, компонента, неймспейса назвать и в него всё сложить.
Здравствуйте, maks1180, Вы писали:
M>Я же написал. M>1.1 — если класс A использует класс B и класс B использует класс A — что очень часто бывает в больших проектах. Тогда приходится некоторые функции выносить в cpp. M>С class forward declarations ты только через ссылки или указатели класс сможешь использовать, что не всегда приемлемо. M>С одним cpp будет поностью компилиться весь проект при любом изменении!