Стиль программирования C++

Я старый (но не слишком старый) программист на 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;
};

И теперь конструктор находится внутри своего класса.

Если ты старый, я уже мертв. (или вы указали неправильный возраст в своем профиле)

Toon Krijthe 17.12.2008 00:50

Я не уверен, почему отдельные файлы заголовков и файлы реализации - это резня ООП, но делайте, что хотите ...

Tim 17.12.2008 00:51

Я просто рад, что мы не замаскируем концепцию объектно-ориентированного программирования, повсюду придерживаясь методов get () set (), которые разрушают всю концепцию инкапсуляции :-)

Martin York 17.12.2008 02:07
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
12
3
5 197
16
Перейти к ответу Данный вопрос помечен как решенный

Ответы 16

Найдите стиль, который подходит вам, как и все остальные. Никто не заставляет вас использовать один из «уродливых» стилей, если только ваш работодатель не применяет руководящие принципы. ;-)

Однако имейте в виду, что размещение определений функций-членов внутри класса, а не снаружи, имеет другую семантику. Когда вы определяете функцию-член внутри класса, она неявно встроена, к лучшему или к худшему.

Я думаю, что плакат просто спрашивает, есть ли причина, отличная от традиции, что классы C++ обычно разбиваются на файлы заголовков и реализации.

Brian 17.12.2008 01:02

Если вы хотите реализовать все свои методы внутри определений классов, у вас будут большие файлы заголовков-монстров (не рекомендуется объявлять классы в файлах cpp) и, возможно, только один файл cpp (тот, который имеет вашу основную функцию).

Так что это вопрос удобства.


Редактировать:

Я считаю, что разделение декларации и реализации действительно полезно. Когда у меня есть классы-монстры с ~ 150 методами, реализация которых создается с использованием множества уловок препроцессора, я часто хочу иметь возможность отличить ...

  • что может делать класс (его интерфейс, заданный его объявлением)
  • как класс это делает (его реализация)

Невозможность сделать это ни на C#, ни на Java довольно неприятно, особенно когда у меня под рукой нет Intellisense.

Я мог видеть это с отсутствием intellisense, но мне нравятся функции навигации в C#, такие как сворачивание областей и раскрывающееся меню всех методов в классе.

Steven Behnke 17.12.2008 01:11

Когда я использую Visual Studio, я часто использую регионы, независимо от того, использую ли я C++ или C#. Я не знаю ни одной Java IDE, которая предлагает такую ​​возможность.

pyon 17.12.2008 01:16

По тону вашего вопроса меня беспокоит, что вы, возможно, читаете какой-то плохой код C++ при изучении C++. Хорошо написанный код, как правило, не является некрасивым ни на одном языке. В качестве отправной точки вы можете попробовать онлайн C++ FAQ, особенно главу по изучению C++.

Стиль вашего программирования зависит от вас. Просто убедитесь, что вы понимаете, какой «уродливый» стиль используют другие.

Если вы хотите заниматься программированием игр, вы, вероятно, захотите работать с другими разработчиками C++, а это значит, что вы должны делать что-то так, как они вас поймут. Если вы вообще надеетесь на сотрудничество, ваш код должен быть в разумном стиле C++. Если вы собираетесь быть разработчиком-одиночкой и ваш код по сути умирает вместе с вами, используйте любой стиль, который вам нравится.

Более того, C++ и Java - два разных языка, и их нужно использовать по-разному. Например, есть причины для файлов заголовков (подумайте об этом как об объявлении интерфейса).

Файлы .h и .cpp часто отделяют объявления от определений, и здесь есть какой-то порядок и концептуальная инкапсуляция. в заголовках указано «что», в файлах реализации - «как».

Я считаю, что все в одном файле на java неорганизовано. Итак, каждому свое.

Все соглашения и идиомы C++ имеют хорошо продуманную логику, как и любой другой язык для идиом, принятых его сообществом.

Я думаю, что лучше всего использовать java (как глагол), когда вы пишете Java, и C++, когда вы пишете C++! Вы поймете почему, имея опыт владения языком. То же, что и переход на любой новый язык.

Это называется разделением интерфейса и реализации. Это позволяет клиентам понять, как вызвать ваш класс, без необходимости продираться через весь код. Честно говоря, я думаю, что это практически безумный, что люди могут посчитать это «неправильным».

Однако вам будет очень приятно узнать, что многие разработчики C++ согласны с вами в этом отношении. Так что продолжайте и поместите всю свою реализацию в файлы * .h. Есть школа стиля, которая с вами согласна.

Я думаю, вы могли бы счесть указанное разделение недостаточным в C++. Объем информации, необходимой для предоставления определений классов и шаблонных функций, огромен по сравнению с интерфейсами в стиле C (или C++, не упрощенным шаблоном). Учитывая это, я вижу преимущество библиотек, содержащих только заголовки.

Tom 17.12.2008 02:12

Когда код компилируется, препроцессор C++ строит единицу трансляции. Он начинается с файла .cpp, анализирует # включает захват текста из заголовков и генерирует один большой большой текстовый файл со всеми заголовками и кодом .cpp. Затем эта единица перевода компилируется в код, который будет работать на целевой платформе. Каждая единица перевода превращается в один объектный файл.

Таким образом, файлы .h включаются в несколько единиц перевода, а файл .cpp просто включается в одну.

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

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

В завершение рассказ о составлении ...
После того, как все единицы перевода будут встроены в объектные файлы (собственный двоичный код для вашей платформы). Компоновщик выполняет свою работу и объединяет их в ваш файл .exe, .dll или .lib. Файл .lib можно связать с другой сборкой, чтобы его можно было повторно использовать в нескольких .exe или .dll.

Я полагаю, что модель компиляции Java более продвинутая, и последствия того, куда вы помещаете свой код, имеют меньшее значение.

Пожалуйста, дайте ссылку на крупномасштабное программирование и стратегии сокращения времени компиляции для полноты.

questzen 17.12.2008 02:10

Вот это: amazon.com/…

Scott Langham 17.12.2008 02:19

А это: stackoverflow.com/questions/373142/…

Scott Langham 17.12.2008 02:25
Ответ принят как подходящий

В дополнение к тому, что здесь говорили другие, есть еще более важные проблемы:

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++ компилируется за один проход и что существуют специальные конструкции, поддерживающие этот стиль (форвардное объявление)

Johannes Schaub - litb 17.12.2008 01:47

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

Johannes Schaub - litb 17.12.2008 01:48

эти две точки примерно соответствуют вашим двум точкам, но ваши точки объясняют синтомы (большие заголовки (из-за отсутствия модулей) / циклические зависимости (помните: компиляция двух проходов, проблем с циклической зависимостью больше нет)).

Johannes Schaub - litb 17.12.2008 01:49

Это совсем не уродливо. Я бы сказал, что это ... мммм по-другому. Перенести свой старый стиль на новую платформу - это некрасиво (я много обсуждал это с мистером Скитом).

Помните, что Ява был определен через несколько лет после 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. у него нет конструктора, который принимает параметры, поэтому он не может использовать список инициализаторов :)

Johannes Schaub - litb 17.12.2008 01:58

Nitpick - я бы не сказал, что Java применяет «чистый объектно-ориентированный объект», я бы сказал, что он обеспечивает «чистый объектно-ориентированный синтаксис». Распространенное заблуждение, что Java == OO только из-за объектов (^:

Tom 17.12.2008 02:18

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++, потому что они не являются общими.

Steve Jessop 17.12.2008 04:03

Я думаю, что многие программисты набили зубы продуктами MicroSoft (и их примерным кодом) и / или программированием для Windows API и ранними соглашениями о кодировании Microsoft, которые использовались во фрагменте кода в вашем вопросе (т. Е. Венгерская нотация, использование заглавных букв определенных типов и т. д..). Мне не нравится смотреть на исходный код от MicroSoft, который выглядит так, будто его пропустили через измельчитель бумаги и склеили обратно. Но уродливое соглашение о кодировании не является функцией или отражением языка C++, который я считаю не более красивым или уродливым, чем большинство других языков.

На самом деле, я считаю, что синтаксис C++ с минимальным количеством ключевых слов, фигурными скобками и богатым набором символических операторов служит для того, чтобы не отвлекать и не вытеснять важные вещи: мои переменные, мои определения типов, мои методы; что, конечно, позволяет мне делать самый красивый код из всех :-)

Раздельное объявление и определение - наименьшее из различий между C++ и Java. Основное отличие современного C++ - важность «семантики значений».

Из-за отсутствия сборки мусора, но отличной поддержки для построения типов, которые ведут себя как автономные значения, хороший стиль C++ включает в себя написание типов значений с правильным поведением с последовательным построением, копированием, присваиванием, заменой и уничтожением.

В частности, очень важно помнить об обмене, потому что он напрямую не поддерживается языком, но действительно должен присутствовать в любом типе, подобном значению.

Посмотрите, как работает стандартная библиотека C++ и как она ожидает поведения ваших типов.

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

Подобно стереотипному программисту, который программирует в одной конкретной парадигме, вы решили, что некоторые вещи, которые вам не знакомы, просто уродливы.

C++ - это другой язык, он многопарадигмальный, и он имеет много переходов от C. Как утверждали другие, так оно и есть.

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

Если вы мне не верите, посмотрите несколько модов для игр. Вполне возможно найти где-нибудь файл заголовка CryEngine2, потому что модам Crysis нужно будет с ним разговаривать, но им не нужно понимать, как он работает. Файлы заголовков будут определять, как в конечном итоге вызывающая функция должна настроить стек для вызова другой функции.

Не буду сокращать, но я думаю, что вам действительно нужно запрограммировать что-то совершенно другое. Изучите другие парадигмы, найдите несколько языков и начните писать игрушечные приложения. Сначала все выглядело бы некрасиво, но со временем вы увидите преимущества.

То же самое я сказал и о функциональном программировании. Теперь я жалуюсь на то, как PHP разделал функциональное программирование с помощью массивов.

Другие вопросы по теме