Стиль программирования C ++ - PullRequest
12 голосов
/ 17 декабря 2008

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

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

Ответы [ 16 ]

24 голосов
/ 17 декабря 2008

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

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

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

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

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

16 голосов
/ 17 декабря 2008

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

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

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

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

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

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

11 голосов
/ 17 декабря 2008
Файлы

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

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

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

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

10 голосов
/ 17 декабря 2008

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

В любом случае, я просто хотел опубликовать другую часть вопроса: является ли C ++ резней ООП?

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

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

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

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

9 голосов
/ 17 декабря 2008

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

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

8 голосов
/ 17 декабря 2008

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

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

6 голосов
/ 17 декабря 2008

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

Итак, наконец, есть ли причина для меня продолжать эту бойню на ООП

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

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

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

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

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

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

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

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

3 голосов
/ 17 декабря 2008

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

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

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

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

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

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

3 голосов
/ 17 декабря 2008

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

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

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

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

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

3 голосов
/ 17 декабря 2008

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...