Ответы пользователя по тегу ООП
  • Нарушают ли указатели и разименование в c++ принципы ООП?

    @Mercury13
    Программист на «си с крестами» и не только
    Не будем встревать в холивар «должен ли указатель быть объектом». Примем, что указатель — простейший тип, ради совместимости и эффективности.
    К простейшему типу инкапсуляция и наследование неприменимы.
    А вот для полиморфизма указатели очень нужны. Динамический полиморфизм — это когда под одним фасадом могут оказаться разные объекты.
    1. По копии их передавать невозможно, только по указателю/ссылке.
    2. Их нельзя уничтожать под одну гребёнку. А значит, если мы их передаём в чьё-то другое владение, надо удостовериться, что они созданы в «куче» и у «фасада» есть виртуальный деструктор.

    Есть ещё два принципа ООП — абстракция и принцип Лисков. Первый имеет отношение к указателям постольку, поскольку есть полиморфизм. Второй — гугли «ковариантные/контравариантные указатели».
    Ответ написан
    1 комментарий
  • Почему в C++ нужно строить всю программу на ООП (длинный вопрос)?

    @Mercury13
    Программист на «си с крестами» и не только
    Задача ООП: 1) Локализовать изменения состояния объекта (инкапсуляция); 2) связывать разные кирпичики данных через стандартные интерфейсы (полиморфизм).

    Простейший тетрис не слишком велик, чтобы его писать на чистом ООП.
    Но представьте себе, мы начинаем налаживать настраиваемое управление джойстиком или клавиатурой. И тогда у нас появляется такой код.
    enum {
      BT_LEFT = 1,
      BT_RIGHT = 2,
      BT_ROTATE = 4,
      BT_SOFTDROP = 8,
      BT_HARDDROP = 16,
      BT_PAUSE = 32,
      BT_CONNECTED = 32768,   // бит, указывающий, что контроллер подключён
    };
    class Controller {  // интерфейс
    public:
      virtual unsigned poll() const = 0;   // сочетание битов BT_XXX
      virtual ~Controller = default;
    };

    Классы Keyboard и Joystick поддерживают интерфейс Controller, и подмена клавиатуры на джойстик и наоборот ничего не изменит.
    Вот вам полиморфизм.

    Текстовый редактор превращаем в многооконный — берём класс Editor и пристраиваем его не к программе в целом, а к MDI-окошку. Вот вам инкапсуляция — локализованное изменение состояния.

    Я как-то мучил движок Doom. Он написан в самом настоящем объектном стиле на чистом Си! Хотя и там были проблемы: сетевой код был куда хуже по качеству, чем сам движок. Писали разделённый экран, глобальную переменную netgame разделили на две, multiplayer и netgame и долго-долго правили баги, где multiplayer, где netgame (было дело, участник десматча ввёл IDKFA, это сработало и вызвало рассинхронизацию). А код пользовательского интерфейса — вообще медвежуть!
    Ответ написан
    Комментировать
  • Почему в интерфейсе могут быть только public методы?

    @Mercury13
    Программист на «си с крестами» и не только
    Private — на что? Ведь тот, кто пойдёт этот интерфейс реализовать, их не увидит.

    С protected штука более сложная. Дело в том, что в классическом интерфейсе ноль кода и данных, и эти protected должен вызывать — кто — разумеется, потомки. Преждевременно увековечивать внутреннюю архитектуру — зачем?

    Встречается ещё такая штука, как «прокачанный интерфейс» — не знаю, как он правильно называется по ООП. Там есть две новых вещи: 1) утилиты — невиртуальные функции, которые представляют собой стандартный сценарий пользования интерфейсом; 2) штатные реализации — некоторым виртуальным функциям придумывают реализацию, которая как-то работает и пользуется другими виртуальными, но потомок, если захочет, может написать более эффективную версию.

    Вот например, утилита.
    // write Intel word
    void st::Stream::writeIW(uint16_t w)
    {
        write(&w, sizeof(uint16_t));
    }
    Утилита не часть интерфейса, и лучший синтаксис для утилит — методы-расширители C#. На худой конец подойдёт простая функция типа Streams.writeIW(stream, 10);.

    Вот, например, штатная реализация.
    // Возвращает остаток потока
    // cu_minus = clipped unsigned minus
    virtual Pos remainder() { return cu_minus<Pos>(size(),pos()); }
    Если в языке нет штатных реализаций, строят класс, где эти функции чем-то реализованы — не всегда, но часто можно унаследоваться от этого класса.

    Раз утилита пользуется обычными общедоступными функциями, нет никакого смысла её кидать в protected (теоретически private/protected могут быть некоторые части сложных утилит). Штатные реализации — тем более, это такая же часть интерфейса, как абстрактные size() и pos().
    Ответ написан
    Комментировать
  • Как реализовать наследование статического поля/метода, если это возможно?

    @Mercury13
    Программист на «си с крестами» и не только
    Первое. Объясни, для себя и для меня, что собой представляет объект Command?

    Моё видение — разделить объекты Command (введённая пользователем и разобранная строка) и Program (программа, реализующая команду). Также я нарисовал — хочешь, используй, хочешь, нет — объект Console (консоль ввода-вывода) и System (окружение программы вроде текущего каталога, переменных окружения, файловой системы).

    Я тут работаю со значениями и указателями, в терминах C++03, но, возможно, вас заинтересуют умные указатели C++11.

    std::string commandLine = console.getSomeCommandLine();
    Command command;
    std::string error;
    if (!command.parse(commandLine, error)) {
      console.err().writeln(error);
      return;
    }
    Program* program = system.findProgram(command.programName);
    if (!program) {
      console.err().writeln("Bad command or file name");
      return;
    }
    Console redirectedConsole = console.redirectStreams(command);
    program->exec(redirectedConsole, system, command.getArguments());


    Второе. Возвращай ссылки, это быстрее.
    const std::vector<std::string>& getArguments() const;
    const std::vector<std::string>& getOptions() const;
    Ответ написан
  • Как определить метод класса, чтобы объект в него передавался не по ссылке?

    @Mercury13
    Программист на «си с крестами» и не только
    UPD. Теперь понял, о чём вы. В таком виде нельзя.
    Ответ написан
  • Объясните толком про интерфейсы в ООП (Delphi). Как их использовать?

    @Mercury13
    Программист на «си с крестами» и не только
    Интерфейсы в Delphi отвечают за две малосвязанных вещи.
    1. Множественное наследование. Об этом уже рассказали до меня, повторяться не буду.
    2. Подсчёт ссылок (для этого реализатор должен корректно поддерживать _AddRef и _Release, но это уже другой вопрос, и подходящая реализация есть в TInterfacedObject).
    Связано это с тем, что Delphi должен был поддерживать Microsoft COM, а там автоматическое управление через подсчёт ссылок.
    Так что интерфейсы часто приплетают только потому, что удобно работать с подсчётом ссылок.

    Вот, например, моя библиотека (обёртка cURL для Delphi) под названием curl4delphi: https://github.com/Mercury13/curl4delphi
    На что тут ICurl? А на то, что это объект с подсчётом ссылок, и для него не надо вызывать деструктор. Пропадает последняя ссылка — объект исчезает. Вот и всё.
    Из-за автодеструкторов, «киллер-фичи» Си++, я в Си++ так не поступал бы.
    Ответ написан
    Комментировать
  • PHP функциональный язык или объектно-ориентированный?

    @Mercury13
    Программист на «си с крестами» и не только
    PHP изначально задумывался скриптовым языком — языком процедурного программирования с динамической типизацией и возможностью вписать пару строк, не оформляя тело. Таким он и остаётся поныне, с вкраплениями ОО. Немного функциональщины, конечно, есть, но это не делает PHP — как, впрочем, Java или C++ — настоящим функциональным языком.
    Ответ написан
    Комментировать
  • Паскаль. Переменные и массивы внутри классов?

    @Mercury13
    Программист на «си с крестами» и не только
    Что творится? Не компилируется?
    А не компилируется из-за непонимания концепции эквивалентности типов. В отличие от Си, внешне одинаковые типы не эквивалентны! Для эквивалентности надо, чтобы их цепочки type A = B; вели к одному «предку». Для этого существует оператор type.
    const
      FieldSize = 10;
      MaxShips = 10;
    type
      TField = record
        cells : array [1..FieldSize, 1..FieldSize] of integer;
        nLive : array [1..MaxShips] of integer;
      end;
      TGame = class
        Field : TField;
        constructor Create(const Field : TField);  
      end;

    Возможно, эквивалентность ослабили в Delphi, не проверял. А в BP именно так.

    Возможно, вы также сделали известную ошибку начинающего дельфиста:
    var
      x : Test;
    ....
    x.Create(a, b);     // неверно!
    x := Test.Create(a, b); // верно!


    Есть одно исключение из этой эквивалентности типов.
    type
      DaInt = array of integer;
    
    procedure DoSomething1(var x : array of integer);
    procedure DoSomething2(var x : DaInt);

    Эти команды обе действуют, но не эквивалентны!

    Первое — нововведение TP7, параметр типа «открытый массив», массив любой размерности. Статический, динамический, строчка 2D-массива — всё подойдёт. Действуют Low, High и (для D4+) Length.

    Второе — нововведение D4, динамический массив, которому можно изменять длину через SetLength.
    Ответ написан
    1 комментарий
  • Как освободить память в java?

    @Mercury13
    Программист на «си с крестами» и не только
    1. Самое простое. var = null; Если нужно ещё и мусорщика пустить — ну пусти, System.gc();
    2. Если нужно, чтобы объект не удерживался — WeakReference. Как только объект исчезнет, слабая ссылка перещёлкивается в null. Бывает нужно: 1) если объекты-дети переживают своих владельцев, и при этом потерять владельца — это несмертельно; 2) когда строим какой-нибудь временный список.
    3. Не выдавать безымянный объект наружу, если он переживает создателя. В безымянных объектах есть ссылка на создателя. Выдавать лямбду: если создатель не нужен, ссылки никакой не будет.
    4. Аналогично с внутренними классами — если он переживает создателя, делай его static.
    5. String.intern, если вы работаете с кучей мелких одинаковых строк. Ну или наладить свой кэш :)
    6. Использовать объектные пулы и прочие структуры, снижающие нагрузку на мусорщик.
    7. Разбивая строки на мелкие кусочки, использовать паттерн doSomething(String data, int start, int length), не вытягивая подстроку физически. Использовать StringBuilder.
    Ответ написан
    1 комментарий
  • Стоит ли вынести объявление типов в отдельный файл?

    @Mercury13
    Программист на «си с крестами» и не только
    Стоит!
    Назовите этот хедер как-нибудь defines.h
    Ответ написан
    Комментировать
  • Как разнести класс по файлам?

    @Mercury13
    Программист на «си с крестами» и не только
    Принцип прост. В .h можно ставить только то, что не производит кода. Как только в проекте появится второй CPP и задействует этот хедер, код будет произведён дважды, и компоновщик (cl/ld/ilink) будет ругаться, что переменная или функция в двух экземплярах. Что именно не производит кода…
    • Определения макросов. Они в принципе кода не производят.
    • Объявление любого типа. Оно лишь говорит об устройстве этого самого типа; код же производят те, кто этим типом пользуются.
    • Шаблоны. Код производит не сам шаблон, а факт расшаблонивания. Разумеется, шаблон может расшаблониться в двух единицах компиляции, но с этим автоматически бороться научились.
    • inline—  код производит не сам inline, а факт включения. inline бывает как явный ключевым словом, так и неявный — в теле класса.
    • Прототипы и extern — они говорят: код есть, но где-то не здесь.
    • Constexpr C++11. Они подставляют значение.
    • Некоторые const в зависимости от компилятора. Например, на Borland const double производит код, а const int — нет.

    Производят код и в хедерах запрещены.
    • Переменная без extern, даже const.
    • Функция, которая не inline.
    • Полностью специализированный шаблон, в котором не осталось шаблонных параметров (template<>).

    Не производят кода, но и лучше закинуть в CPP.
    • Некоторые скрытые (private) inline и шаблоны, если они не используются из хедера.
    Ответ написан
    3 комментария
  • Как объяснить кусок кода C++?

    @Mercury13
    Программист на «си с крестами» и не только
    Весь этот код (за исключением Close) — автогенерируемый.

    ///// Защита от повторного включения
    #ifndef Unit1H
    #define Unit1H
    
    ///// Хедеры VCL. Причём всё это сделано так, чтобы упростить написание ценой удлинения
    ///// компиляции. Более громоздкий, но и более удачный вариант.
    ///// В H:
    /////   namespace Controls { class TLabel; }
    /////   using namespace Controls;
    ///// В CPP:
    /////   #include <Controls.hpp>
    ///// Вот таким образом можно (было) избавиться от каскадного подключения
    ///// хедера Controls. А то каждый, кто использует главной форму,
    ///// автоматически подключает эти хедеры.
    #include <Classes.hpp>
    #include <Controls.hpp>
    #include <StdCtrls.hpp>
    ///// Только от Forms.hpp избавиться таким макаром нельзя:
    ///// мы наследуемся от TForm.
    #include <Forms.hpp>
    
    ///// Класс формы. Все формы наследуются от TForm.
    class TForm1 : public TForm
    {
       ///// Особое право доступа Borland, для совместимости с Delphi.
       ///// Поля и свойства published не просто public, но включаются
       ///// в структуру рефлексии (aka reflection или introspection)
       ///// и программа о них знает при выполнении.
       ///// Применительно к формам — published-поля доступны
       ///// загрузчику.
    __published: // IDE-managed Components
       ///// Компоненты, которые мы установили на форме редактором.
    TLabel *Label1;
    TButton *Button1;
       ///// События, которые мы прописали в редакторе.
       ///// __fastcall — модель вызова, аналогичная Delphi.
       ///// Именно такая модель вызова принята в обработчиках
       ///// событий.
    void __fastcall Button1Click(TObject *Sender);
       ///// Пользователь пока не прописал никаких своих
       ///// полей и функций.
    private: // User declarations
    public: // User declarations
       ///// Конструктор. Раз уж у формы нетривиальный конструктор —
       ///// по правилам Си++ его надо повторить в подклассе.
       ///// Снова-таки, модель вызова __fastcall: в формах Delphi
       ///// используются т.н. виртуальные конструкторы, 
       ///// когда по имени класса можно создать объект этого класса.
       ///// Фабричный метод, только немного лучше.
       ///// Но это значит: у всех подчинённых классов
       ///// должен быть один и тот же набор параметров
       ///// и модель вызова.
    __fastcall TForm1(TComponent* Owner);
    };
    //---------------------------------------------------------------------------
    ///// Как известно, переменная объявляется один раз.
    ///// Поскольку хедер может подключаться к огромному числу CPP,
    ///// её объявляют как extern (она есть, но в другом месте).
    ///// Макрос PACKAGE раскрывается в __declspec(package),
    ///// чтобы эту штуку можно было собрать как пакет.
    extern PACKAGE TForm1 *Form1;
    //---------------------------------------------------------------------------
    #endif

    Модель вызова — это как технически мы вызываем подпрограмму. Какая память и какие регистры на это используются, и кто подчищает стек. Ищи в Википедии.
    Ответ написан
    Комментировать
  • Для чего используются "методы по умолчанию" на практике?

    @Mercury13
    Программист на «си с крестами» и не только
    Это приближает интерфейсы Java к примесям.

    1. Очень часто бывает, что у какой-то функции есть реализация, опирающаяся на другие функции — либо базовая неоптимальная, либо вообще единственно возможная. Чаще всего это избыточные функции-утилиты. Пишу на Си++
    class Stream {
    public:
      virtual void write(size_t length, const void* data) = 0;
    
      // пишет в поток word в машинном порядке байтов
      void writeW(uint16_t data) {
        write(2, &data);
      }
    };

    За что вообще так ненавидят множественное наследование? За дублирование данных! Вот у нас некая штука, без единого поля данных — почему в Java она класс, а не интерфейс?

    2. Какую-то функцию переопределяют настолько редко, что лучше сделать ей реализацию на месте. Опять Си++.
    class ErpConnector {
    public:
      // 80% модулей не могут экспортировать данные в систему управления
      //   предприятием — напишем базовую реализацию.
      virtual bool canExportData() const { return false; }
      virtual void exportData() const {}
    };


    3. И просто документирование, как оно должно себя вести :) Из реального проекта, снова Си++.
    void im::DateGrouper::toFirstDateOfPeriod(dt::Date& aDate, int aStart) const
    {
        aDate = toDate(toInt(aDate, aStart), aStart);
    }

    Класс служит для группировки дат в периоды (недели, месяцы, годы). aStart — это дополнительный int, позволяющий начать год с февраля или месяц с 15-го. Эта функция переводит дату «на месте» в первую дату периода. Она крайне неоптимальна (дату в число, затем число опять в дату) и потому переписана почти во всех в реализациях.
    void im::MonthGrouper::toFirstDateOfPeriod(dt::Date& aDate, int aStart) const
    {
        if (aDate.day < aStart)
            aDate.addMonthsMechanical(-1);  // механически вычесть 1 месяц; дата может стать неверной.
        aDate.day = aStart;
        aDate.fixupTo1();    // неверную дату привести к 1-му числу следующего месяца
    }


    Но по крайней мере понятно, как она должна работать.
    Ответ написан
    Комментировать
  • Как присвоить выражение boolean полю TObject?

    @Mercury13
    Программист на «си с крестами» и не только
    Попробую угадать задачу. Есть некое поле (назовём его UserData), но у нас-то пользовательские данные boolean!

    Решение 1. Прямое преобразование, невзирая на несовместимости типов.
    someObj.UserData := TObject(true);
    someBool := boolean(someObj.UserData);

    + Эффективнее всех.
    − Запрещено разыменовывать.

    Решение 2. Создать некий объект DummyObject. Обозначить nil=false, <>nil = true.
    someObj.UserData := dummyObject;
    someBool := (someObj.UserData <> nil);
    
    initialization
      dummyObject := TObject.Create;
    finalization
      dummyObject.Free;
    end.

    + Достаточно эффективно, можно разыменовывать.
    − Если в классе есть неотключаемое автоуничтожение UserData — облом!

    Решение 3. Создать класс TBoolObject.
    TBoolObject = class
    public
      Value : boolean;
      // конструкторы опущу, деструктор не нужен
    end.
    
    someObj.UserData := TBoolObject(true);
    someBool := (someObj.UserData as TBoolObject).Value;

    + Работает, когда в классе есть автоуничтожение UserData.
    − Иначе — овчина не стоит выделки, тогда нам эти объекты придётся уничтожать самим.

    Если же у вас тут экономия памяти и надо в одной памяти держать object или boolean, работает решение 4.
    TObjBool = record
    case integer of
    0 : ( asObj : TObject );
    1 : ( asBool : boolean );
    end;

    + Очень эффективно.
    − Программист сам должен следить за тем, что там: объект или буль.
    Ответ написан
    2 комментария
  • Абстрактные классы и интерфейсы - когда применять одно или другое?

    @Mercury13
    Программист на «си с крестами» и не только
    Всё зависит от того, что вы хотите. И вообще, с «опциональной» функциональностью есть много вариантов (для простоты пишу на Java).

    1. «Типа по ООП».
    class Animal {}
    class Dog extends Animal {
       public void doSound() {}
    }

    Недостаток: если у нас есть собака со звуком, кошка со звуком и утка со звуком и надо выдать звук, если возможно — фигвам!

    2. Реализовать интерфейс Vocal
    interface Vocal {
      void doSound();
    }
    class Animal {}
    class Dog extends Animal implements Vocal {
       @Override
       public void doSound();
    }

    В таком случае
    if (animal instanceof Vocal)
      ((Vocal)animal).doSound().

    Впрочем, такие преобразования типа — тоже слегка не по ООП.

    3. Не в курсе, возможно ли в Java, напишу это на C++. Protected doSound в Animal и public в Dog.
    class Animal {
    protected:
       void doSound();
    };
    class Dog : public Animal {
    public:
      using Animal::doSound;
    };

    Недостаток в том, что если всё же придётся организовывать общую функциональность — то приходится писать шаблон «Public Морозов».
    Пример: у всех компонентов VCL есть protected __property Caption. И в 99% случаев этого хватает: заглавие отображается где-то — вытягивай наружу. У меня возник вопрос с автоматическим переводом форм. Либо подключай интроспекцию, либо Public Морозов (в Delphi/Builder есть интроспекция и доп. право доступа published, подключающее свойство к ней). Я не стал мучиться и сделал второе.
    Также задача несколько неудобна, когда библиотека долго живёт и развивается: с каждой новой версией приходится выносить наружу всё новые и новые свойства
    Плюсы? Просто, малый расход памяти и удобно писать специальные задачи. Например, свойство Hint protected, но действует; если всплывающая подсказка какая-нибудь динамическая и снаружи менять нельзя — меняй на здоровье изнутри.

    ЗЫ. Пришёл с работы, то же самое на Java.
    public class Dog extends Animal {
        @Override
        public void doSound() { super.doSound(); }
    }

    Ну а роль Морозова будут играть API интроспекции и «морозовский» класс в том же пакете. Все мы забываем, что protected покрывает более жёсткое package, т.е. из того же пакета тоже можно.
    public static void main(String[] args) {
            Animal an = new Animal();
            an.doSound();   // protected!
        }


    4. Может ли издавать звук?
    class Animal {
    public boolean isVocal() { return false; }
    public void doSound() {}
    }


    5. Вернуть интерфейс Vocal; если null — животное молчит.
    class Animal {
    public Vocal getVocal() { return null; }
    }


    Что выбирать — однозначного ответа нет. Насколько много будут наследовать от этого класса, насколько много будет общих задач и насколько будет перегруженной документация… Допустим, если мы не имеем доступа к классу Animal, заманчиво второе. А если Vocal — не интерфейс, а абстрактный класс, то пятое.
    Ответ написан
    Комментировать
  • Наследование C++?

    @Mercury13
    Программист на «си с крестами» и не только
    Private и protected — это когда объект скрывает, что унаследован от студента. Снаружи не виден ни факт наследования, ни отцовские поля.
    class Father {};
    class Son : private Father {};
    
    int main()
    {
        Son son;
        Father& q = son;
    }

    error: 'Father' is an inaccessible base of 'Son'

    Private и protected — скорее «хаки» и пользоваться ими не рекомендуется. Хотя, разумеется, пользуются, чтобы упростить себе жизнь. Я вижу два назначения: 1) хорошая основа для совершенно постороннего класса; 2) реализация интерфейса, которая нужна только внутри.

    Вот пример второго. Объект FileWriter реализует интерфейс Callback для своих внутренних нужд.
    #include <iostream>
    
    class Callback {
    public:
        virtual void act(int x) = 0;
    };
    
    void generateFibonacci(int n, Callback& callback) {
        int x1 = 0, x2 = 1;
        for (int i = 0; i < n; ++i) {
            callback.act(x2);
            int x3 = x1 + x2;
            x1 = x2;
            x2 = x3;
        }
    }
    
    class FileWriter : private Callback {
    public:
        FileWriter(std::ostream& aOs, int aN) : os(aOs), n(aN) {}
        void run() { generateFibonacci(n, *this); }
    private:
        std::ostream& os;
        const int n;
        void act(int x) override { os << x << std::endl; }
    };
    using namespace std;
    
    int main()
    {
        FileWriter wri(std::cerr, 10);
        wri.run();
    }

    А если реальная жизнь — то объект может быть одновременно QDialog (диалоговое окно) и QAbstractItemModel (модель данных для какой-нибудь таблицы, лежащей на этом окошке). Хотя, повторяю, это скорее хак.

    P.S. В Delphi и Java всё наследование публичное, и если нужно скрытно реализовать какой-то интерфейс или задействовать удобный класс — то только скрытым полем. Так, как в комментариях.

    P.P.S. Пример первого. Какой-нибудь Array2d скрыто наследуется от Array1d, потому что у него совершенно другой интерфейс. У Array1d — alloc(n) и operator()(i), у Array2d — alloc(m, n) и operator()(i, j). А Array1d — неплохая штука, чтобы управляться блоком памяти длиной m×n элементов.
    Ответ написан
  • Где и в каких случаях правильно использовать extern?

    @Mercury13
    Программист на «си с крестами» и не только
    Назначение extern единственное. Мы говорим, что этой переменной нет в данной единице компиляции, но не надо переживать, она есть в другой и компоновщик её подкомпонует.

    По факту является extern’ом static-поле класса. Это решение принято Строуструпом по простой причине: определение класса может промелькнуть несколько раз в разных единицах компиляции, а определение переменной должно быть одно на программу.

    Если ваш код не работает без extern — это значит, что где-то нашлась глобальная переменная по имени client, и для корректной работы вебмастера потребовалось обращаться к этому нашему клиенту. По факту — очень навороченный способ создать static-поле.

    Таким образом, у вас три варианта.

    1. Если Ethernet-клиент один на всех вебмастеров, вам нужно static-поле.
    // HEADER
    class WebMaster {
    public:
        WebMaster();
        bool connect_open();
    private:
        static EthernetClient client;
    }
    
    // CPP
    EthernetClient WebMaster::client;


    Впрочем, этот вариант крайне маловероятен.

    2. Скорее всего, у вас один вебмастер — один клиент и, вероятно, вам нужно обычное нестатическое поле.
    class WebMaster {
    public:
        WebMaster();
        bool connect_open();
    private:
        EthernetClient client;
    }


    3. Если у нас приходится привязывать мастеров к клиентам, приходится использовать указатели и ссылки. В простейшем виде…
    // HEADER
    class WebMaster {
    public:
        WebMaster(EthernetClient& aClient);
        bool connect_open();
    private:
        EthernetClient& client;
    }
    
    // CPP
    WebMaster::WebMaster(EthernetClient& aClient) : client(aClient) {}
    
    ...
    EthernetClient client;
    WebMaster webmaster(client);
    Ответ написан
    Комментировать
  • Как поместить "нетривиальный" объект в стуктуру?

    @Mercury13
    Программист на «си с крестами» и не только
    Одно из полей (в данном случае data в реализации item<Edge>) не имеет конструктора по умолчанию. Есть три пути.

    РАЗ. Придумать, как сделать, чтобы конструктор всё-таки был.
    class Edge : public EdgeBase
    {
    public:
      Edge (Point* firstPoint, Point* secondPoint) { init(firstPoint, secondPoint); }
      Edge () { init(NULL, NULL); }
      void init (Point* firstPoint, Point* secondPoint)
      {
        this->firstPoint = firstPoint;
        this->secondPoint = secondPoint;
      }
    private:
      Point* firstPoint;
      Point* secondPoint;
    };


    ДВА. Если объект копируется/переносится, можно воспользоваться такой штукой. С вашего позволения, упрощу задачу и вместо Edge заведу другой класс — ImmutableInt. Правда, этот код — попытка совместить ежа с ужом (непонятно, какую концепцию должен поддерживать Payload), но, тем не менее, работает.
    #include <iostream>
    
    class ImmutableInt
    {
    public:
        explicit ImmutableInt(int x) : fData(x) {}
        int data() const { return fData; }
    private:
        int fData;
    };
    
    template <class Payload>
    class ListItem
    {
    public:
        Payload payload;
        ListItem* next;
    
        ListItem() : next(NULL) {}
        ListItem(const Payload& x) : payload(x), next(NULL) {}
    };
    
    // Так писать нельзя — эта конструкция расшаблонивает все функции,
    // а конструктора по умолчанию как не было, так и нет!
    //template class ListItem<ImmutableInt>;
    
    int main()
    {
        ListItem<ImmutableInt> li(ImmutableInt(42));
        std::cout << li.payload.data() << std::endl;
        return 0;
    }


    ТРИ. Воспользоваться переменными шаблонами (variadic templates) C++11.
    #include <iostream>
    
    class ImmutableInt
    {
    public:
        explicit ImmutableInt(int x) : fData(x) {}
        int data() const { return fData; }
    private:
        int fData;
    };
    
    template <class Payload>
    class ListItem
    {
    public:
        Payload payload;
        ListItem* next = nullptr;
    
        template<class... Args>ListItem(Args... args) : payload(args...) {}
    };
    
    int main()
    {
        ListItem<ImmutableInt> li(42);
        std::cout << li.payload.data() << std::endl;
        return 0;
    }


    P.S. Хотя ImmutableInt — семантически тот же int и теоретически explicit не надо, всё-таки отметил — просто чтобы показать, что во втором случае мы передаём параметром ImmutableInt<42>, а в третьем — 42.

    P.P.S. Я упомянул слово «концепция». Это набор требований к типу. Что-то типа интерфейса — но, во-первых, никак не связано с ООП и его динамическим полиморфизмом, и, во-вторых, не столь жёсткое: как ты интерфейсом из Java наладишь концепцию «есть конструктор копирования» или «может делить себя на число и что-то выходит»? Синтаксическая поддержка концепций откладывается на C++17, но уже в C++03 было несколько концепций: InputIterator, DefaultConstructible, CopyAssignable и другие. А пока… концепция не поддерживается — ошибка компиляции где-то в страшенном стандартном хедере.

    P.P.P.S. Написав код
    template<typename type_t>
    struct item
    {
      item* next;
      type_t data;
    };

    вы автоматически потребовали от type_t концепцию DefaultConstructible (есть конструктор по умолчанию)
    Ответ написан
    1 комментарий
  • Как изменять значения переменных другого класса в C++?

    @Mercury13
    Программист на «си с крестами» и не только
    Не буду говорить о качестве кода, const-корректности и прочей бяке. Конкретная ваша ошибка
    int& operator [](int i) // именно ссылка!

    У вас возвращается значение — временный объект, которому, разумеется, невозможно присвоить что бы то ни было.
    Ответ написан
  • В какой момент пора использовать ООП?

    @Mercury13
    Программист на «си с крестами» и не только
    ООП, как известно, упрощает разработку программ, состоящих из взаимодействующих компонентов с меняющимся состоянием. В вебе этого мало, и потому можно быть успешным вебистом и не знать ООП. ООП даёт двоякий выигрыш.

    1) Инкапсуляция — мы прячем внутреннее состояние, давая его менять специальными выведенными наружу «рычажками».
    • Тесная работа с коммуникационными протоколами (например, почтой).
    • Поддержка какой-то вещи с меняющимся состоянием (в вебе этого мало — может, какая-нибудь автоматическая вёрстка?)

    2) Абстракция и полиморфизм — в общем, поддержка разных вещей под общим фасадом.
    • Неопределённость в технологиях — может, MySQL, а может, SqLite. Тогда создаём абстрактный класс «БД» и от него наследуем MySQL и SqLite.
    • Какие-нибудь штуки из предметной отрасли. Пишем игру — персонажей игры удобно так держать. Хотя можно ли написать многопользовательскую игру целиком на PHP — в этом я не уверен.
    • Ну, не знаю, где ещё. Настольная/мобильная версия, что ли?
    Ответ написан
    Комментировать