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

Я старый (но не слишком старый) Java-программист, который решил изучить С++. Но я видел, что большая часть стиля программирования на C++ - это... ну, просто чертовски уродливый!

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

Итак, наконец, есть ли у меня причина продолжать эту резню в ООП и все, что хорошо и праведно в программировании, или я могу просто игнорировать эти старомодные соглашения на С++ и использовать мой хороший стиль программирования Java

Кстати, я изучаю С++, потому что хочу заниматься программированием игры.

Вот пример:

На веб-сайте С++ я нашел реализацию 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;
};

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

Ответ 1

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

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();
}

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

Заголовки просто предоставляют декларации, в то время как исходные файлы предоставляют определения. Или еще один способ сказать: заголовки сообщают вам, что там (какие символы действительны для использования), а источник сообщает компилятору, что на самом деле делают символы. В С++ вам не нужно ничего, кроме действительного символа, чтобы начать использовать то, что есть.

Доверяйте, что у С++ есть причина для этой идиомы, потому что, если вы этого не сделаете, у вас будет много головных болей для вас. Я знаю:/

Ответ 2

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

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

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

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

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

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

Ответ 3

Файлы

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

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

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

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

Ответ 4

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

Во всяком случае, я просто хотел опубликовать в другой части вопроса: есть ли С++ OOP masacre?

С++ - многостраничный язык. Он позволяет procedural, объектно-ориентированный и generic. И это добродетель, а не недостаток. Вы по-прежнему можете использовать чистый ООП, если хотите, но ваш код, несомненно, выиграет от обучения и знания, когда использовать другие парадигмы.

В качестве примера мне не нравятся классы , где все функции-члены являются статическими, а данных нет. Нет причин создавать класс утилиты больше, чем просто группировать набор бесплатных функций под общим именем. Вы должны сделать это на Java, поскольку он настаивает на чистом синтаксисе OO, который, как и commaed Tom, не совпадает с реальным OO. В С++ определение пространства имен и набора бесплатных функций предлагает аналогичное решение без необходимости блокировать создание объектов, объявляя частный конструктор, а также позволяя пользователям создавать объекты, не содержащие смысла, из пустого класса.

Для меня опыт (я когда-то был программистом только на Java) научил меня, что существуют различные инструменты, которые лучше подходят для разных задач. Я еще не нашел золотой молоток и преимущество С++ заключается в том, что вы можете выбрать другой молот для каждой отдельной задачи, даже в том же компиляционном блоке.

Коррекция: litb сообщает мне в комментарии, что WNDCLASS - это структура в API win32. Так что критика класса, не использующего списки инициализации, является простой бессмыслицей, и поэтому я удаляю ее.

Ответ 5

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

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

Ответ 6

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

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

Ответ 7

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

Итак, наконец, есть ли какая-то причина для продолжения этой резни в ООП

Ну, вызывающие функции, не принадлежащие к классам, не OOP - это процедурное программирование. Таким образом, я считаю, что вам действительно трудно вырваться из менталитета ООП. (Конечно, у С++ много проблем, но процедурное программирование не является одним из них.)

С++ - это язык с несколькими парадигмами, а не только язык OO. Шаблоны - это форма общего программирования, которое может быть применено к процедурной, ООП и метапрограммированию парадигм С++. В следующем стандарте С++ вы также увидите некоторые функциональные парадигмы.

Все, что помещает определение класса в файл заголовка и методы в другом исходном файле;

Это происходит от языка программирования C, еще в 70-х годах. С++ был разработан для обратной совместимости с C.

Язык программирования D - это попытка исправить многие проблемы С++ (и, как я сказал ранее, их много), но поддерживать хорошие возможности С++ - включая все различные парадигмы, поддерживаемые С++: процедурные, ООП, метапрограммирование и функциональность.

Если вы хотите выйти из менталитета ООП, попробуйте D!. Тогда, когда вы почувствуете, как смешивать и сопоставлять различные парадигмы, вы можете (если хотите) прийти вернуться на С++ и научиться разбираться с его синтаксисом и другими проблемами.

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

Удачи в ваших приключениях!

Ответ 8

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

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


Edit:

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

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

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

Ответ 9

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

Помните Java был определен через годы после С++, поэтому разумно, что они исправили некоторые конструкции (таким же образом С# learn от Java).

Итак, я бы предложил сохранить этот стиль С++.

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

Я думаю, что С++ не имеет ключевого слова "interface", и именно так вы отделяете интерфейс класса от его реализации. Аналогично этому в Java:

public interface Some { 
    public void method();
}

public class SomeImpl implements Some { 
    public void method(){}
}

Ответ 10

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

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

Ответ 11

Отдельное объявление и определение - это наименьшее из различий между С++ и Java. Важнейшим отличием современного С++ является важность "семантики значений".

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

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

Посмотрите, как работает стандартная библиотека С++ и как она ожидает, что ваши типы будут вести себя.

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

Ответ 12

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

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

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

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

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

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

Ответ 13

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

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

Ответ 14

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

Ответ 15

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

Ответ 16

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

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