Конструктор (объектно-ориентированное программирование)

Перейти к навигацииПерейти к поиску

В объектно-ориентированном программировании конструктор класса (от англ. constructor) — специальный блок инструкций, вызываемый при создании объекта.

Назначение конструктора

Одна из ключевых особенностей ООП — инкапсуляция: внутренние поля класса напрямую недоступны, и пользователь может работать с объектом только как с единым целым, через открытые (public) методы. Каждый метод, в идеале, должен быть устроен так, чтобы объект, находящийся в «допустимом» состоянии (то есть когда выполняется инвариант класса), после вызова метода также оказался в допустимом состоянии. И первая задача конструктора — перевести поля объекта в такое состояние.

Вторая задача — упростить пользование объектом. Объект — не «вещь в себе», ему часто приходится требовать какую-то информацию от других объектов: например, объект File, создаваясь, должен получить имя файла. Это можно сделать и через метод:

  File file;
  file.open("in.txt", File::omRead);

Но удобнее открытие файла сделать в конструкторе:[1]

  File file("in.txt", File::omRead);

Виды конструкторов

Разнообразные языки программирования представляют несколько разновидностей конструкторов:

  • конструктор с параметрами;
  • конструктор по умолчанию, не принимающий аргументов;
  • именованный конструктор — функция, предполагающая явный вызов по имени, работающая как конструктор
  • конструктор копирования — конструктор, принимающий в качестве аргумента объект того же класса (или ссылку из него);
  • конструктор преобразования — конструктор, принимающий один аргумент (эти конструкторы могут вызываться автоматически для преобразования значений других типов в объекты данного класса).
  • конструктор перемещения (специфично для C++11)
class Complex
{
 public:
  // Конструктор по умолчанию 
  // (в данном случае является также и конструктором преобразования)
  Complex(double i_re = 0, double i_im = 0)
      : re(i_re), im(i_im)
  {}

  // Конструктор копирования
  Complex(const Complex &obj)
  {
   re = obj.re;
   im = obj.im;
  }
  private:
    double re, im;
};

Конструктор с параметрами

Конструкторы, принимающие один или более аргументов, называются параметризованными. Например:

class Example
{
     int x, y;
   public:
     Example();
     Example(int a, int b); // параметризованный конструктор 
};
Example :: Example()
{
}
Example :: Example(int a, int b)
{
     x = a;
     y = b;
}

Параметризованный конструктор может быть вызван явно или неявно, например:

    Example e = Example(0, 50); // явный вызов

    Example e(0, 50);           // неявный вызов

Конструктор по умолчанию

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

Именованный конструктор

Конструктор копирования

Конструктор, аргументом которого является ссылка на объект того же класса. Применяется в C++ для передачи объектов в функции по значению.

Конструктор копирования в основном необходим, когда объект имеет указатели на объекты, выделенные в куче. Если программист не создаёт конструктор копирования, то компилятор создаст неявный конструктор копирования, который копирует указатели как есть, то есть фактическое копирование данных не происходит и два объекта ссылаются на одни и те же данные в куче. Соответственно попытка изменения «копии» повредит оригинал, а вызов деструктора для одного из этих объектов при последующем использовании другого приведёт к обращению в область памяти, уже не принадлежащую программе.

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

Конструктор преобразования

Конструктор, принимающий один аргумент. Задаёт преобразование типа своего аргумента в тип конструктора. Такое преобразование типа неявно применяется только если оно уникально.

Определенное пользователем преобразование типа может иметь одну из двух форм: - из классового типа C в любой тип T, для чего у С должен быть C::operator T() - из любого типа T в классовый тип C, для чего у C должен быть C::C(T) (или же C::C(T&), или же C::C(T&&))

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

Если конструктор (или operator T()) помечен ключевым словом explicit, то такое преобразование типа применяется только при наличии явной операции приведения типа вида (T)C или же static_cast<T>C. Если же слова explicit нет, то компилятор может вставить такое преобразование даже неявно, например, при вызове функции f(T arg) в виде f(C).

Конструктор перемещения

В C++11 появился новый тип неконстантных ссылок, носящий название «ссылка на праводопустимое выражение» (англ. rvalue reference) и обозначаемый как T&&, и новый вид конструкторов — конструкторы перемещения (англ. move constructors). Конструктор перемещения принимает на входе значение неконстантной ссылки на объект класса, и используется для передачи владения ресурсами этого объекта. Конструкторы перемещения были придуманы для решения проблемы потери эффективности, связанной с созданием временных объектов.

Виртуальный конструктор

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

«Виртуальными конструкторами» называют похожий, но другой механизм, присутствующий в некоторых языках — например, он есть в Delphi, но нет в C++ и Java. Этот механизм позволяет создать объект любого заранее неизвестного класса при двух условиях:

  • этот класс является потомком некоего наперёд заданного класса (в данном примере это класс TVehicle);
  • на всём пути наследования от базового класса к создаваемому цепочка переопределения не обрывалась. При переопределении виртуального метода синтаксис Delphi требует ключевое слово overload, чтобы старая и новая функции с разными сигнатурами могли сосуществовать, override для переопределения функции либо reintroduce для задания новой функции с тем же именем — последнее недопустимо.
type
  TVehicle = class
      constructor Create;  virtual;
    end;

  TAutomobile = class (TVehicle)
      constructor Create;  override;
    end;

  TMotorcycle = class (TVehicle)
      constructor Create;  override;
    end;

  TMoped = class (TMotorcycle)  // обрываем цепочку переопределения - заводим новый Create
      constructor Create(x : integer);  reintroduce;
    end;

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

type
  CVehicle = class of TVehicle;

Такой механизм позволяет создавать объекты любого заранее неизвестного класса, производного от TVehicle.

var
  cv : CVehicle;
  v : TVehicle;

cv := TAutomobile;
v := cv.Create;

Заметьте, что код

cv := TMoped;
v := cv.Create;

является некорректным — директива reintroduce разорвала цепочку переопределения виртуального метода, и в действительности будет вызван конструктор TMotorcycle.Create (а значит, будет создан мотоцикл, а не мопед!)

См. также Фабрика (шаблон проектирования)

Синтаксис

C++

Имя конструктора должно совпадать с именем класса. Допускается использовать несколько конструкторов с одинаковым именем, но различными параметрами.

Пример

class ClassWithConstructor {
 public:
  /* Инициализация внутреннего объекта с помощью конструктора */
  ClassWithConstructor(float parameter): object(parameter) {}/* вызов конструктора AnotherClass(float); */
 private:
  AnotherClass object;
};

Python

В языке Python конструктор состоит из двух методов класса: __new__ и __init__. Метод __new__ создаёт объект, а метод __init__ инициализирует объект.

Пример

class ClassWithConstructor:
    def __new__(cls):
        """This method is the first part of the constructor."""
        return super().__new__(cls)

    def __init__(self):
        """This method is the second part of the constructor."""
        pass

Ruby

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

Пример

class ClassWithConstructor
    def initialize
        print 'This method is constructor.'
    end
end

Delphi

В Delphi, в отличие от C++, для объявления конструктора служит ключевое слово constructor. Имя конструктора может быть любым, но рекомендуется называть конструктор Create.

Пример

  TClassWithConstructor = class
    public
    constructor Create;
  end;

Java

Некоторые отличия между конструкторами и другими методами Java:

  • конструкторы не имеют типа возвращаемых данных (на самом деле они всегда возвращают this);
  • конструкторы не могут напрямую вызываться (необходимо использовать ключевое слово new);
  • конструкторы не могут иметь модификаторы synchronized, final, abstract, native и static;

Пример

public class Example {
  private int data;

  // Конструктор по умолчанию, data инициализируется 1, при создании экземпляра класса Example
  public Example() {
    data = 1;
  }

  // Перегрузка конструктора
  public Example(int input) {
    data = input;
  }
}
// код, иллюстрирующий создание объекта описанным выше конструктором
Example e = new Example(42);

JavaScript

В JavaScript в качестве конструктора выступает обычная функция, используемая в качестве операнда оператора new. Для обращения к созданному объекту используется ключевое слово this.

Однако в спецификации ECMAScript 6 были добавлена синтаксическая оболочка прототипов, которой присущи такие свойства ООП как наследование, а также небольшой список обязательных методов, например: toString().

Пример

function Example(initValue) {
    this.myValue = initValue;
}

Example.prototype.getMyValue = function() {
    return this.myValue; 
}

//ES6 class
class Example {
    constructor() {
        console.log('constructor');
    }
}
// код, иллюстрирующий создание объекта описанным выше конструктором
var exampleObject = new Example(120);

Visual Basic .NET

Конструкторы в Visual Basic .NET используют обычный метод объявления с именем New.

Пример

Class Foobar
  Private strData As String
  
  ' Constructor
  Public Sub New(ByVal someParam As String)
     strData = someParam
  End Sub
End Class
' некий код
' иллюстрирующий создание объекта описанным выше конструктором
Dim foo As New Foobar(".NET")

C#

Пример

class MyClass
{
  private int _number;
  private string _string;

  public MyClass(int num, string str)
  {
    _number = num;
    _string = str;
  }
}
// Код, иллюстрирующий создание объекта описанным выше конструктором
MyClass example = new MyClass(42, "string");

Эйфель

В Эйфеле подпрограммы, которые инициализируют объекты, называются процедурами создания. Процедуры создания в чём-то подобны конструкторам и в чём-то отличаются. Они имеют следующие характеристики:

  • Процедуры создания не имеют никакого явного типа результата возврата (по определению процедуры[Примечание 1]).
  • процедуры создания поименованы (имена ограничены допустимыми идентификаторами);
  • процедуры создания задаются по именам в тексте класса;
  • процедуры создания могут быть вызваны напрямую (как обычные процедуры) для повторной инициализации объектов;
  • каждый эффективный (то есть конкретный, не абстрактный) класс должен (явно или неявно) указать по крайней мере одну процедуру создания;
  • процедуры создания отвечают за приведение только что проинициализированного объекта в состояние, которое удовлетворяет инварианту класса[Примечание 2].

Хотя создание объекта является предметом некоторых тонкостей [Примечание 3], создание атрибута с типовым объявлением x: T, выраженном в виде инструкции создания create x.make состоит из следующей последовательности шагов:

  • создать новый непосредственный экземпляр типа T[Примечание 4];
  • выполнить процедуру создания make для вновь созданного экземпляра;
  • прикрепить вновь созданный объект к сущности x.

Пример

В первом отрывке ниже определяется класс POINT. Процедура make кодируется после ключевого слова feature.

Ключевое слово create вводит список процедур, которые могут быть использованы для инициализации экземпляров класса. В данном случае список содержит default_create, процедуру с пустой реализацией, унаследованной из класса ANY, и процедуру make с реализацией в самом классе POINT.

class
    POINT
create
    default_create, make

feature

    make (a_x_value: REAL; a_y_value: REAL)
        do
            x := a_x_value
            y := a_y_value
        end

    x: REAL
            -- Координата X

    y: REAL
            -- Координата Y
        ...

Во втором отрывке класс, являющийся клиентом класса POINT, имеет объявления my_point_1 и my_point_2 типа POINT.

В коде подпрограммы my_point_1 создаётся с координатами (0.0; 0.0). Поскольку в инструкции создания не указана процедура создания, используется процедура default_create, унаследованная из класса ANY. Эта же строка могла бы быть переписана как create my_point_1.default_create. Только процедуры, указанные как процедуры создания могут использоваться в инструкциях создания (то есть в инструкциях с ключевым словом create).

Следующей идёт инструкция создания для my_point_2, задающая начальные значения для координат my_point_2.

Третья инструкция осуществляет обычный вызов процедуры make для ре-инициализации экземпляра, прикреплянного к my_point_2, другими значениями.

    my_point_1: POINT
    my_point_2: POINT
        ...

            create my_point_1
            create my_point_2.make (3.0, 4.0)
            my_point_2.make (5.0, 8.0)
        ...

ColdFusion

Пример

Необходимо отметить, что в ColdFusion не существует метода-конструктора. Широкое распространение среди сообщества программистов на ColdFusion получил способ вызова метода 'init', выступающего в качестве псевдоконструктора.

<cfcomponent displayname="Cheese">
   <!--- свойства --->
   <cfset variables.cheeseName = "" />
   <!--- псевдоконструктор --->
   <cffunction name="init" returntype="Cheese">
      <cfargument name="cheeseName" type="string" required="true" />
      <cfset variables.cheeseName = arguments.cheeseName />
      <cfreturn this />
   </cffunction>
</cfcomponent>

PHP

Пример

В PHP (начиная с версии 5) конструктор — это метод __construct(), который автоматически вызывается ключевым словом new после создания объекта. Обычно используется для выполнения различных автоматических инициализаций, как например, инициализация свойств. Конструкторы также могут принимать аргументы, в этом случае, когда указано выражение new, необходимо передать конструктору формальные параметры в круглых скобках.

class Person
{
   private $name;

   function __construct($name)
   {
       $this->name = $name;
   }
 
   function getName()
   {
       return $this->name;
   }
}

Тем не менее, конструктор в PHP версии 4 (и ранее) — метод класса с именем этого же класса.

class Person
{
   private $name;

   function Person($name)
   {
       $this->name = $name;
   }
 
   function getName()
   {
       return $this->name;
   }
}

Perl

Пример

В Perl конструктор должен применить функцию bless к некой переменной (обычно ссылке на хеш):

package Example;

sub new {
  my $class = shift;
  my $self  = {};
  return bless $self, $class;
}

1;

Но это минимальный базовый вариант, есть множество более продвинутых способов, начиная от use fields и заканчивая Moose.

Упрощенные конструкторы (с псевдокодом)

Конструкторы всегда являются частью реализации классов. Класс (в программировании) описывает спецификации основных характеристик набора объектов, являющихся членами класса, а не отдельные характеристики какого-либо объекта из них. Рассмотрим простую аналогию. Возьмем в качестве примера набор (или класс, используя его более общее значение) учеников некоторой школы. Таким образом мы имеем:

class Student {
    // описание класса учеников
    // ... прочий код ...
}

Тем не менее, класс Student — всего лишь общий шаблон (прототип) наших школьников. Для его использования программист создает каждого школьника в виде объекта или сущности (реализации) класса. Этот объект является тем реальным фрагментом данных в памяти, чьи размер, шаблон, характеристики и (в некоторой мере) поведение определяются описанием класса. Обычный способ создания объектов — вызов конструктора (классы в общем случае могут иметь отдельные конструкторы). Например,

class Student {
    Student (String studentName, String Address, int ID) {
        // ... здесь храним вводимые данные и прочие внутрнние поля ...
    }
    // ...
}

См. также

Примечания

  1. Подпрограммы Эйфеля являются либо процедурами либо функциями. У процедур нет никакого возвращаемого типа. Функции всегда имеют возвращаемый тип.
  2. Поскольку должен быть также удовлетворён инвариант наследуемого(-х) класса(-ов), нет обязательного требования вызова родительских конструкторов.
  3. Полная спецификация содержится в стандартах ISO/ECMA по языку программироная Эйфель в онлайн доступе.[2]
  4. Стандарт Эйфеля требует, чтобы поля были инициализированы при первом доступе к ним, т.ч. нет необходимости осуществлять их инициализацию значениями по умолчанию во время создания объекта.

Ссылки

  1. Конечно, это приводит к определённым техническим трудностям — например, что будет, если из конструктора выпадет исключение? Впрочем, разработчик класса просто должен выполнять требования языка, а в большинстве программ не требуется детальная диагностика и автоматические повторы при ошибках.
  2. ISO/ECMA документ описания Эйфеля. Дата обращения: 19 апреля 2009. Архивировано 16 июня 2008 года.