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 ().