10 Конструкторы и деструкторы
Конструкторы
Существует небольшое, но очень важное различие между конструкторами и деструкторами в C ++ и методами __construct () и __destruct () в PHP.
Конструктор C ++ вызывается на объект, который инициализируется, но еще не находится в инициализированном состоянии. Вы можете испытать это, вызвав виртуальный метод из конструктора. Даже когда этот виртуальный метод был переопределен в производном классе, он всегда будет выполнять метод самого класса, а не переопределенную реализацию. Причина этого в том, что во время вызова конструктора C ++ объект еще не полностью инициализирован, и объект еще не знает о его позиции в иерархии классов. Таким образом, вызов виртуального метода не может быть передан производному объекту.
Однако в PHP метод __construct () имеет другое поведение. Когда он вызывается, объект уже инициализирован, и вполне законно совершать вызовы даже абстрактных методов, которые реализуются в производных классах. Следующий PHP-скрипт полностью действителен, но невозможно сделать аналогичную вещь в C ++.
<?php // базовый класс в PHP, в котором вызывается абстрактный метод abstract class BASE { // конструктор public function __construct() { // метод абстрактного вызова $this->doSomething(); } // абстрактный метод, реализуемый производными классами public abstract function doSomething(); } // производный класс class DERIVED extends BASE { // реализовать абстрактный метод public function doSomething() { echo("doSomething()\n"); } } // создать экземпляр производного класса $d = new DERIVED(); ?>
Этот скрипт выводит «doSomething ()». Причиной этого является то, что __construct () вообще не является конструктором, но является очень обычным методом, который просто является первым вызванным методом и который вызывается автоматически после создания объекта.
Эта разница важна для вас как программиста на C ++, потому что вы никогда не должны путать свой конструктор C ++ с методом PHP __construct (). В конструкторе C ++ объект строится, и не все данные еще доступны. Виртуальные методы не могут быть вызваны, и объект также еще не существует в пользовательском пространстве PHP.
После того, как конструктор закончен, PHP-механизм берет управление и создает объект PHP, а библиотека PHP-CPP связывает этот объект PHP с вашим объектом C ++. Только после того, как объект PHP и объект C ++ будут полностью построены, метод __construct () вызывается - как обычный метод. Поэтому не редкость иметь как конструктор C ++, так и метод __construct () в вашем классе. Конструктор C ++ для инициализации переменных-членов и __construct () для активации объекта.
#include <phpcpp.h> /** * Простой класс счетчика */ class Counter : public Php::Base { private: /** * Внутреннее значение * @var int */ int _value = 0; public: /** * конструктор c ++ */ Counter() = default; /** * c ++ деструктор */ virtual ~Counter() = default; /** * php "конструктор" * @param params */ void __construct(Php::Parameters ¶ms) { // скопировать первый параметр (если имеется) if (!params.empty()) _value = params[0]; } /** * функции для увеличения и уменьшения */ Php::Value increment() { return ++_value; } Php::Value decrement() { return --_value; } Php::Value value() const { return _value; } }; /** * Переключитесь в контекст C, чтобы функция get_module () * могла быть вызвана C-программами (с помощью механизма Zend) */ extern "C" { /** * Функция запуска для расширения * @return void* */ PHPCPP_EXPORT void *get_module() { static Php::Extension myExtension("my_extension", "1.0"); // описание класса, чтобы PHP знал, какие методы доступны Php::Class<Counter> counter("Counter"); counter.method<&Counter::__construct>("__construct"); counter.method<&Counter::increment>("increment"); counter.method<&Counter::decrement>("decrement"); counter.method<&Counter::value>("value"); // добавить класс к расширению myExtension.add(std::move(counter)); // вернуть расширение return myExtension; } }
Вышеприведенный код показывает, что __construct () зарегистрирован, как если бы он был обычным методом - и это то, что он есть. Пример, который мы использовали ранее (тот, у кого есть класс Counter), теперь расширен, так что можно дать ему начальное значение счетчику, передав значение «конструктору».
<?php $counter = new Counter(10); $counter->increment(); echo($counter->value()."\n"); ?>
Поскольку метод __construct () рассматривается как обычный метод, вы также можете указать его параметры и является ли этот метод общедоступным, приватным или защищенным. __construct () также непосредственно вызывается из пространства пользователя PHP, поэтому производные методы могут явно вызывать parent :: __ construct ().
Частные конструкторы
Как и любой другой метод, метод __construct () также может быть помечен как private или protected. Если вы сделаете это, вы сделаете невозможным создание экземпляров вашего класса из PHP-скриптов. Важно понимать, что конструктор C ++ и деструктор C ++ по-прежнему вызываются в таких ситуациях, потому что это вызов __construct (), который будет терпеть неудачу, а не фактическую конструкцию объекта.
Да, действительно: если вы сделаете метод __construct () private, а внутри скрипта PHP выполняется вызов «new Counter ()», библиотека PHP-CPP сначала создаст экземпляр нового экземпляра вашего класса, а затем сообщит об ошибке, поскольку Метод __construct () является private, а затем сразу же уничтожает объект (и вызывает деструктор C ++).
#include <phpcpp.h> /** * Переключитесь в контекст C, чтобы функция get_module () * могла быть вызвана C-программами (с помощью механизма Zend) */ extern "C" { /** * Начальная точка расширения * @return void* */ PHPCPP_EXPORT void *get_module() { static Php::Extension myExtension("my_extension", "1.0"); // описание класса, чтобы PHP знал, какие методы доступны Php::Class<Counter> counter("Counter"); // добавьте к классу частный метод __construct, чтобы объекты не могли // быть построены из PHP-скриптов. Имейте в виду, что конструктор C ++ // вызывает вызов - это будет вызов первой функции __construct (), // которая завершится неудачно, а не фактическая конструкция объекта. counter.method<&Counter::__construct>("__construct", Php::Private); ... } }
Клонирование объектов
Если ваш класс имеет конструктор копирования, он автоматически становится клонированным. Если вы не хотите, чтобы ваш класс мог быть клонирован скриптами PHP, вы можете сделать две вещи: вы можете либо удалить конструктор копирования из своего класса, либо зарегистрировать частный метод __clone (), как мы зарегистрировали приватный __construct () ранее.
#include <phpcpp.h> /** * Простой класс счетчика */ class Counter : public Php::Base { private: /** * Internal value * @var int */ int _value = 0; public: /** * конструктор c ++ */ Counter() = default; /** * Удалить конструктор копирования * * Удалив конструктор копирования, оператор клонирования PHP * будет автоматически деактивирован. PHP будет вызывать ошибку, * если объект пытается клонировать. * * @param counter */ Counter(const Counter &counter) = delete; /** * деструктор c ++ */ virtual ~Counter() = default; /** * php "конструктор" * @param params */ void __construct(Php::Parameters ¶ms) { // скопировать первый параметр (если имеется) if (!params.empty()) _value = params[0]; } /** * функции для увеличения и уменьшения */ Php::Value increment() { return ++_value; } Php::Value decrement() { return --_value; } Php::Value value() const { return _value; } }; /** * Переключитесь в контекст C, чтобы функция get_module () * могла быть вызвана C-программами (с помощью механизма Zend) */ extern "C" { /** * Функция запуска для расширения * @return void* */ PHPCPP_EXPORT void *get_module() { static Php::Extension myExtension("my_extension", "1.0"); // описание класса, чтобы PHP знал, какие методы доступны Php::Class<Counter> counter("Counter"); counter.method<&Counter::__construct>("__construct"); counter.method<&Counter::increment>("increment"); counter.method<&Counter::decrement>("decrement"); counter.method<&Counter::value>("value"); // альтернативный способ сделать объект непригодным counter.method("__clone", Php::Private); // добавить класс к расширению myExtension.add(std::move(counter)); // вернуть расширение return myExtension; } }
В приведенном выше примере мы показали, что оба способа сделать объект непригодным. Использование только одного из них уже достаточно.
Построение объектов
Класс Php::Value может использоваться как регулярная $переменная PHP, и поэтому вы можете использовать ее для хранения экземпляров объекта. Но как вы создаете новые объекты? Для этого у нас есть класс Php::Object, который является просто переопределенным классом Php::Value с альтернативными конструкторами и некоторыми дополнительными проверками, чтобы предотвратить использование объекта Php::Object для хранения значений, отличных от объектов.
// новая переменная, удерживающая строку "Counter" Php::Value counter0("Counter"); // новая переменная, удерживающая вновь созданный объект типа "Counter", // __construct () вызывается без параметров Php::Object counter1("Counter"); // новая переменная, удерживающая вновь созданный объект, и // __construct () вызывается со значением 10 Php::Object counter2("Counter", 10); // новый встроенный объект DateTime, построенный с помощью "now" Php::Object time("DateTime", "now"); // valid, Php :: Object - расширенное значение Php :: Value и // может быть присвоен базовому объекту Php :: Value Php::Value copy1 = counter1; // недопустимый оператор, Php:: Object может использоваться только для хранения объектов Php::Object copy2 = counter0;
Конструктор объекта Php::Object принимает имя класса и необязательный список параметров, которые будут переданы функции __construct (). Вы можете использовать имена из встроенных классов PHP и других расширений (например, DateTime), классов из вашего расширения (например, Counter) и даже классов из пользовательского пространства PHP.
Класс Php::Object также может использоваться, если вы хотите создать экземпляр собственного C ++-класса без вызова функции __construct (). Это может быть полезно, например, когда метод __construct () является закрытым или когда вы хотите обойти вызов с помощью собственного метода __construct ().
#include <phpcpp.h> // фактическая реализация класса class Counter : public Php::Base { private: int _value = 0; public: // конструктор c ++ Counter(int value) : _value(value) {} // деструктор c ++ virtual ~Counter() = default; // php "конструктор" void __construct() {} // функции для увеличения и уменьшения Php::Value value() const { return _value; } }; // для создания нового таймера Php::Value createTimer() { return Php::Object("Counter", new Counter(100)); } extern "C" { PHPCPP_EXPORT void *get_module() { static Php::Extension myExtension("my_extension", "1.0"); // описание класса, чтобы PHP знал, какие методы доступны, // метод __construct является закрытым, поскольку скриптам PHP // не разрешено создавать экземпляры Counter Php::Class<Counter> counter("Counter"); counter.method<&Counter::__construct>("__construct", Php::Private); counter.method<&Counter::value>("value"); // добавить класс к расширению myExtension.add(std::move(counter)); // добавьте заводскую функцию для создания таймера на расширение myExtension.add("createTimer", createTimer); // вернуть расширение return myExtension; } }
В приведенном выше коде мы создали функцию __construct () класса Counter. Это делает невозможным создание экземпляров этого класса - как из PHP-скриптов пользователя, так и через вызовы Php::Object("Counter"), потому что при построении объектов этими способами в конечном итоге будет создан запрещенный вызов __construct ().
У Php :: Object есть альтернативный синтаксис, который берет указатель на класс C ++ (выделенный в куче, с оператором new!) И который превращает этот указатель в переменную PHP без вызова метода __construct (). Обратите внимание, что вы также должны указать имя класса, потому что классы C ++ не содержат никакой информации о себе (например, их имя), тогда как в PHP такая информация требуется для обработки отражений и функций, таких как get_class ().