Я старый (но не слишком старый) программист на Java, который решил изучить C++. Но я видел, что большая часть стиля программирования C++ ... ну, чертовски уродлива!
Все эти вещи, связанные с помещением определения класса в файл заголовка и методов в другой исходный файл - Вызов функций из ниоткуда вместо использования методов внутри классов. Все это кажется ... неправильным!
Итак, наконец, есть ли у меня какая-то причина продолжать эту резню в отношении ООП и всего хорошего и праведного в программировании, или я могу просто игнорировать эти старомодные соглашения C++ и использовать свой хороший стиль программирования Java?
Кстати, я изучаю C++, потому что хочу заниматься программированием игр.
Вот пример:
На веб-сайте C++ я нашел реализацию для Windows:
class WinClass
{
public:
WinClass (WNDPROC wndProc, char const * className, HINSTANCE hInst);
void Register ()
{
::RegisterClass (&_class);
}
private:
WNDCLASS _class;
};
Этот класс находится в файле заголовка и в конструкторе:
WinClass::WinClass (WNDPROC wndProc, char const * className, HINSTANCE hInst)
{
_class.style = 0;
_class.lpfnWndProc = wndProc; // Window Procedure: mandatory
_class.cbClsExtra = 0;
_class.cbWndExtra = 0;
_class.hInstance = hInst; // Owner of the class: mandatory
_class.hIcon = 0;
_class.hCursor = ::LoadCursor (0, IDC_ARROW); // Optional
_class.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1); // Optional
_class.lpszMenuName = 0;
_class.lpszClassName = className; // Mandatory
}
Находится в исходном файле .cpp.
Что я мог просто сделать, так это:
class WinClass
{
public:
WinClass (WNDPROC wndProc, char const * className, HINSTANCE hInst)
{
_class.style = 0;
_class.lpfnWndProc = wndProc; // Window Procedure: mandatory
_class.cbClsExtra = 0;
_class.cbWndExtra = 0;
_class.hInstance = hInst; // Owner of the class: mandatory
_class.hIcon = 0;
_class.hCursor = ::LoadCursor (0, IDC_ARROW); // Optional
_class.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1); // Optional
_class.lpszMenuName = 0;
_class.lpszClassName = className; // Mandatory
}
void Register ()
{
::RegisterClass (&_class);
}
private:
WNDCLASS _class;
};
И теперь конструктор находится внутри своего класса.
Я не уверен, почему отдельные файлы заголовков и файлы реализации - это резня ООП, но делайте, что хотите ...
Я просто рад, что мы не замаскируем концепцию объектно-ориентированного программирования, повсюду придерживаясь методов get () set (), которые разрушают всю концепцию инкапсуляции :-)





Найдите стиль, который подходит вам, как и все остальные. Никто не заставляет вас использовать один из «уродливых» стилей, если только ваш работодатель не применяет руководящие принципы. ;-)
Однако имейте в виду, что размещение определений функций-членов внутри класса, а не снаружи, имеет другую семантику. Когда вы определяете функцию-член внутри класса, она неявно встроена, к лучшему или к худшему.
Я думаю, что плакат просто спрашивает, есть ли причина, отличная от традиции, что классы C++ обычно разбиваются на файлы заголовков и реализации.
Если вы хотите реализовать все свои методы внутри определений классов, у вас будут большие файлы заголовков-монстров (не рекомендуется объявлять классы в файлах cpp) и, возможно, только один файл cpp (тот, который имеет вашу основную функцию).
Так что это вопрос удобства.
Редактировать:
Я считаю, что разделение декларации и реализации действительно полезно. Когда у меня есть классы-монстры с ~ 150 методами, реализация которых создается с использованием множества уловок препроцессора, я часто хочу иметь возможность отличить ...
Невозможность сделать это ни на C#, ни на Java довольно неприятно, особенно когда у меня под рукой нет Intellisense.
Я мог видеть это с отсутствием intellisense, но мне нравятся функции навигации в C#, такие как сворачивание областей и раскрывающееся меню всех методов в классе.
Когда я использую Visual Studio, я часто использую регионы, независимо от того, использую ли я C++ или C#. Я не знаю ни одной Java IDE, которая предлагает такую возможность.
По тону вашего вопроса меня беспокоит, что вы, возможно, читаете какой-то плохой код C++ при изучении C++. Хорошо написанный код, как правило, не является некрасивым ни на одном языке. В качестве отправной точки вы можете попробовать онлайн C++ FAQ, особенно главу по изучению C++.
Стиль вашего программирования зависит от вас. Просто убедитесь, что вы понимаете, какой «уродливый» стиль используют другие.
Если вы хотите заниматься программированием игр, вы, вероятно, захотите работать с другими разработчиками C++, а это значит, что вы должны делать что-то так, как они вас поймут. Если вы вообще надеетесь на сотрудничество, ваш код должен быть в разумном стиле C++. Если вы собираетесь быть разработчиком-одиночкой и ваш код по сути умирает вместе с вами, используйте любой стиль, который вам нравится.
Более того, C++ и Java - два разных языка, и их нужно использовать по-разному. Например, есть причины для файлов заголовков (подумайте об этом как об объявлении интерфейса).
Файлы .h и .cpp часто отделяют объявления от определений, и здесь есть какой-то порядок и концептуальная инкапсуляция. в заголовках указано «что», в файлах реализации - «как».
Я считаю, что все в одном файле на java неорганизовано. Итак, каждому свое.
Все соглашения и идиомы C++ имеют хорошо продуманную логику, как и любой другой язык для идиом, принятых его сообществом.
Я думаю, что лучше всего использовать java (как глагол), когда вы пишете Java, и C++, когда вы пишете C++! Вы поймете почему, имея опыт владения языком. То же, что и переход на любой новый язык.
Это называется разделением интерфейса и реализации. Это позволяет клиентам понять, как вызвать ваш класс, без необходимости продираться через весь код. Честно говоря, я думаю, что это практически безумный, что люди могут посчитать это «неправильным».
Однако вам будет очень приятно узнать, что многие разработчики C++ согласны с вами в этом отношении. Так что продолжайте и поместите всю свою реализацию в файлы * .h. Есть школа стиля, которая с вами согласна.
Я думаю, вы могли бы счесть указанное разделение недостаточным в C++. Объем информации, необходимой для предоставления определений классов и шаблонных функций, огромен по сравнению с интерфейсами в стиле C (или C++, не упрощенным шаблоном). Учитывая это, я вижу преимущество библиотек, содержащих только заголовки.
Когда код компилируется, препроцессор C++ строит единицу трансляции. Он начинается с файла .cpp, анализирует # включает захват текста из заголовков и генерирует один большой большой текстовый файл со всеми заголовками и кодом .cpp. Затем эта единица перевода компилируется в код, который будет работать на целевой платформе. Каждая единица перевода превращается в один объектный файл.
Таким образом, файлы .h включаются в несколько единиц перевода, а файл .cpp просто включается в одну.
Если файлы .h содержат много материала (включая реализацию), то единицы перевода будут соответственно больше. Время компиляции увеличится, и когда вы измените что-либо в заголовке ... каждую единицу перевода, которая его использует, потребуется перекомпилировать.
Итак ... сведение к минимуму содержимого файлов .h значительно сокращает время компиляции. Вы можете отредактировать файл .cpp, чтобы изменить функцию, и нужно перестроить только эту единицу перевода.
В завершение рассказ о составлении ...
После того, как все единицы перевода будут встроены в объектные файлы (собственный двоичный код для вашей платформы). Компоновщик выполняет свою работу и объединяет их в ваш файл .exe, .dll или .lib. Файл .lib можно связать с другой сборкой, чтобы его можно было повторно использовать в нескольких .exe или .dll.
Я полагаю, что модель компиляции Java более продвинутая, и последствия того, куда вы помещаете свой код, имеют меньшее значение.
Пожалуйста, дайте ссылку на крупномасштабное программирование и стратегии сокращения времени компиляции для полноты.
Вот это: amazon.com/…
А это: stackoverflow.com/questions/373142/…
В дополнение к тому, что здесь говорили другие, есть еще более важные проблемы:
1) Большие единицы перевода приводят к увеличению времени компиляции и увеличению размеры объектных файлов.
2) Круговые зависимости! И это большой. И это может почти всегда исправлять путем разделения заголовков и источника:
// Vehicle.h
class Wheel {
private:
Car& m_parent;
public:
Wheel( Car& p ) : m_parent( p ) {
std::cout << "Car has " << m_parent.numWheels() << " wheels." << std::endl;
}
};
class Car {
private:
std::vector< Wheel > m_wheels;
public:
Car() {
for( int i=0; i<4; ++i )
m_wheels.push_back( Wheel( *this ) );
}
int numWheels() {
return m_wheels.size();
}
}
Независимо от того, в каком порядке вы их разместите, в одном всегда будет отсутствовать определение другого, даже с использованием форвардных объявлений это не сработает, поскольку в телах функций используются особенности о символах каждого класса.
Но если вы разделите их на соответствующие файлы .h и .cpp и используете форвардные объявления, это удовлетворит компилятор:
//Wheel.h
//-------
class Car;
class Wheel {
private:
Car& m_parent;
public:
Wheel( Car& p );
};
//Wheel.cpp
//---------
#include "Wheel.h"
#include "Car.h"
Wheel::Wheel( Car& p ) : m_parent( p ) {
std::cout << "Car has " << m_parent.numWheels() << " wheels." << std::endl;
}
//Car.h
//-----
class Wheel;
class Car {
private:
std::vector< Wheel > m_wheels;
public:
Car();
int numWheels();
}
//Car.cpp
//-------
#include "Car.h"
#include "Wheel.h"
Car::Car() {
for( int i=0; i<4; ++i )
m_wheels.push_back( Wheel( *this ) );
}
int Car::numWheels() {
return m_wheels.size();
}
Теперь код, который действительно должен знать особенности второго класса, может просто включать файл заголовка, которому не нужно знать особенности первого класса.
Заголовки просто предоставляют декларации, в то время как исходные файлы предоставляют определения. Или иначе: заголовки сообщают вам, что там есть (какие символы можно использовать), а источник сообщает компилятору, что на самом деле делают символы. В C++ вам не нужно ничего, кроме допустимого символа, чтобы начать использовать то, что он есть.
Поверьте, у C++ есть причина для этой идиомы, потому что, если вы этого не сделаете, вы создадите себе много головной боли в будущем. Я знаю :/
это единственный найденный мной ответ, который на самом деле не похож на «java делает это неправильно; разделение - это хорошо», но на самом деле дает причину того, почему это так. Вы могли бы упомянуть, что C++ компилируется за один проход и что существуют специальные конструкции, поддерживающие этот стиль (форвардное объявление)
и что не существует концепции модуля, и поэтому файлы заголовков требуются для предоставления информации, обычно содержащейся в метаинформации о модулях.
эти две точки примерно соответствуют вашим двум точкам, но ваши точки объясняют синтомы (большие заголовки (из-за отсутствия модулей) / циклические зависимости (помните: компиляция двух проходов, проблем с циклической зависимостью больше нет)).
Это совсем не уродливо. Я бы сказал, что это ... мммм по-другому. Перенести свой старый стиль на новую платформу - это некрасиво (я много обсуждал это с мистером Скитом).
Помните, что Ява был определен через несколько лет после C++, поэтому разумно, что они исправили некоторые конструкции (так же, как C# учится у Java).
Итак, я бы посоветовал придерживаться этого стиля C++.
Подумай об этом. Заголовочные файлы похожи на объявления интерфейса, а файлы cpp - это реализация.
Я думаю, что в C++ нет ключевого слова interface, и таким образом вы отделяете интерфейс класса от его реализации. Подобно этому в Java:
public interface Some {
public void method();
}
public class SomeImpl implements Some {
public void method(){}
}
Если вы пишете C++ DLL и помещаете какой-либо код в заголовки, вы получите сложный для отладки беспорядок. DLL будет содержать код из файлов .cpp, но пользователи библиотеки DLL будет содержать часть кода, встроенного в себя.
Это действительно плохо в Windows, где вы сталкиваетесь с такими вещами, как разные распределители кучи в разных частях программы, и если вы измените реализацию, вызывающие вашу DLL будут использовать старый код из встроенных заголовков.
Большинство ответов касается разделения заголовков и единиц компиляции. Я согласен с большинством, вы должны использовать его, потому что он более эффективен, чище, удобнее для коллег или просто потому, что ... (и вы заметите преимущество во времени компиляции вскоре после этого).
В любом случае, я просто хотел опубликовать другую часть вопроса: является ли C++ маскировкой ООП?
C++ - это многопарадигмальный язык. Это позволяет программировать процессуальный, объектно-ориентированный и общий. И это достоинство, а не недостаток. Вы по-прежнему можете использовать чистый ООП, если хотите, но ваш код, несомненно, выиграет от изучения и знания, когда использовать другие парадигмы.
Например, мне не нравится служебные классы, где все функции-члены статичны и нет данных. Нет причин для создания служебного класса, кроме как просто группировать набор бесплатных функций под общим именем. Вы должны сделать это на Java, поскольку он настаивает на чистом OO-синтаксисе, который, как указано в Том, не совпадает с реальным OO. В C++ определение пространства имен и набора бесплатных функций предлагает аналогичное решение без необходимости блокировать создание объектов, объявляя частный конструктор или позволяя пользователям создавать экземпляры бессмысленных объектов из пустого класса.
Для меня опыт (когда-то я был программистом только на Java) научил меня, что существуют разные инструменты, которые лучше подходят для решения различных задач. Я еще не нашел золотой молоток а преимущество C++ заключается в том, что вы можете выбрать разные молотки для каждой задачи, даже в одном и том же модуле компиляции.
Исправление: литб сообщает мне в комментарии, что WNDCLASS - это структура в Win32 API. Так что критика класса, не использующего списки инициализации, является полной ерундой, и поэтому я ее удаляю.
ну, я думаю, что WNDCLASS - это структура Win32 API. у него нет конструктора, который принимает параметры, поэтому он не может использовать список инициализаторов :)
Nitpick - я бы не сказал, что Java применяет «чистый объектно-ориентированный объект», я бы сказал, что он обеспечивает «чистый объектно-ориентированный синтаксис». Распространенное заблуждение, что Java == OO только из-за объектов (^:
Calling functions out of nowhere, instead of using methods inside classes; All that just seems... wrong!
So finally, is there any reason for me to continue with this massacre to the OOP
Что ж, вызов функций, не принадлежащих классам, - это не ООП - это процедурное программирование. Таким образом, я считаю, что вам действительно трудно отказаться от мышления ООП. (Конечно, у C++ много недостатков, но процедурное программирование не входит в их число.)
C++ - это мультипарадигмальный язык, а не только объектно-ориентированный язык. Шаблоны - это форма общего программирования, которая может применяться к парадигмам процедурного, ООП и метапрограммирования C++. В следующем стандарте C++ вы также увидите добавление некоторых функциональных парадигм.
All that stuff of putting the class definition in a header file, and the methods in a different source file;
Это происходит из языка программирования C еще в 70-х годах. C++ был разработан для обратной совместимости с C.
Язык программирования D - это попытка исправить многие недостатки C++ (а, как я уже сказал ранее, их много), но при этом сохранить хорошие возможности C++, включая все различные парадигмы, поддерживаемые C++: процедурные, ООП, метапрограммирование и функциональный.
Если вы хотите избавиться от мышления ООП, попробуйте D! Затем, когда вы почувствуете, как смешивать и сопоставлять разные парадигмы, вы можете (при желании) вернуться к C++ и научиться справляться с его синтаксисом и другими проблемами.
P.S. Я ежедневно использую C++, и я его фанат - это мой любимый язык программирования. Но, как знает любой опытный программист на C++, у C++ есть свои недостатки. Но как только вы овладеете различными парадигмами и научитесь обходить недуги C++, в вашем распоряжении будет невероятно мощный инструмент (поскольку это все, чем является язык).
Удачи в приключениях!
Согласовано. В любом случае в Java есть классы, состоящие только из статических методов (например, java.lang.Math). Наличие фальшивого класса в качестве заполнителя не делает это «более ООП», чем бесплатные функции в C++, а некоторые из них менее эффективны, чем эквиваленты C++, потому что они не являются общими.
Я думаю, что многие программисты набили зубы продуктами MicroSoft (и их примерным кодом) и / или программированием для Windows API и ранними соглашениями о кодировании Microsoft, которые использовались во фрагменте кода в вашем вопросе (т. Е. Венгерская нотация, использование заглавных букв определенных типов и т. д..). Мне не нравится смотреть на исходный код от MicroSoft, который выглядит так, будто его пропустили через измельчитель бумаги и склеили обратно. Но уродливое соглашение о кодировании не является функцией или отражением языка C++, который я считаю не более красивым или уродливым, чем большинство других языков.
На самом деле, я считаю, что синтаксис C++ с минимальным количеством ключевых слов, фигурными скобками и богатым набором символических операторов служит для того, чтобы не отвлекать и не вытеснять важные вещи: мои переменные, мои определения типов, мои методы; что, конечно, позволяет мне делать самый красивый код из всех :-)
Раздельное объявление и определение - наименьшее из различий между C++ и Java. Основное отличие современного C++ - важность «семантики значений».
Из-за отсутствия сборки мусора, но отличной поддержки для построения типов, которые ведут себя как автономные значения, хороший стиль C++ включает в себя написание типов значений с правильным поведением с последовательным построением, копированием, присваиванием, заменой и уничтожением.
В частности, очень важно помнить об обмене, потому что он напрямую не поддерживается языком, но действительно должен присутствовать в любом типе, подобном значению.
Посмотрите, как работает стандартная библиотека C++ и как она ожидает поведения ваших типов.
Старайтесь стремиться (не всегда возможно) к полному отсутствию голых указателей или использования оператора new. Скройте такие детали внутри классов, чтобы гарантировать их правильное использование, заключив их в семантику значений.
Подобно стереотипному программисту, который программирует в одной конкретной парадигме, вы решили, что некоторые вещи, которые вам не знакомы, просто уродливы.
C++ - это другой язык, он многопарадигмальный, и он имеет много переходов от C. Как утверждали другие, так оно и есть.
Заголовочные файлы предназначены для компилятора, чтобы проверить базовый синтаксис, не зная реализации. Если вы заядлый программист на Java, вы должны быть хорошо знакомы со всей программой и концепцией интерфейса. Подумайте о файле заголовка, где находится интерфейс.
Если вы мне не верите, посмотрите несколько модов для игр. Вполне возможно найти где-нибудь файл заголовка CryEngine2, потому что модам Crysis нужно будет с ним разговаривать, но им не нужно понимать, как он работает. Файлы заголовков будут определять, как в конечном итоге вызывающая функция должна настроить стек для вызова другой функции.
Не буду сокращать, но я думаю, что вам действительно нужно запрограммировать что-то совершенно другое. Изучите другие парадигмы, найдите несколько языков и начните писать игрушечные приложения. Сначала все выглядело бы некрасиво, но со временем вы увидите преимущества.
То же самое я сказал и о функциональном программировании. Теперь я жалуюсь на то, как PHP разделал функциональное программирование с помощью массивов.
Если ты старый, я уже мертв. (или вы указали неправильный возраст в своем профиле)