Виртуальный метод

Поделись знанием:
(перенаправлено с «Виртуальная функция»)
Перейти к: навигация, поиск

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

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

Одни языки программирования (например, C++, C#) требуют явно указывать, что данный метод является виртуальным. В других языках (например, Java, Python) все методы являются виртуальными по умолчанию (но только те методы, для которых это возможно; например в Java методы с доступом private не могут быть переопределены в связи с правилами видимости).

Базовый класс может и не предоставлять реализации виртуального метода, а только декларировать его существование. Такие методы без реализации называются «чисто виртуальными» (перевод англ.  pure virtual) или абстрактными. Класс, содержащий хотя бы один такой метод, тоже будет абстрактным. Объект такого класса создать нельзя (в некоторых языках допускается, но вызов абстрактного метода приведёт к ошибке). Наследники абстрактного класса должны предоставить реализацию для всех его абстрактных методов, иначе они, в свою очередь, будут абстрактными классами. Абстрактный класс, который содержит только чисто виртуальные методы называется интерфейсом.

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





Пример виртуальной функции на C++

Пример на C++, иллюстрирующий отличие виртуальных функций от невиртуальных:

Предположим, базовый класс Animal (животное) может иметь виртуальный метод eat (кушать). Подкласс (класс-потомок) Fish (рыба) переопределит метод eat() не так как его переопределит подкласс Wolf (волк), но можно вызвать eat() на любом экземпляре класса, унаследованного от класса Animal, и получить поведение eat(), соответствующее данному подклассу.

Это позволяет программисту обрабатывать список объектов класса Animal, вызывая над каждым объектом метод eat(), не задумываясь о том к какому подклассу принадлежит текущий объект (то есть как питается конкретное животное).

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

class Animal {
public:
    void /*невиртуальный*/ move() { 
        std::cout << "This animal moves in some way" << std::endl; 
    }
    virtual void eat() {
        std::cout << "Animal ate something!" << std::endl; 
    }
    virtual ~Animal(){} // деструктор
};

class Wolf : public Animal {
public:
    void move() { 
        std::cout << "Wolf walks" << std::endl; 
    }
    void eat(void) { // метод eat переопределён и тоже является виртуальным
        std::cout << "Wolf eats meat!" << std::endl; 
    }
};

int main() {
    Animal* zoo[] = {new Wolf(), new Animal()};
    for(Animal* a:zoo) {
        a->move();
        a->eat();
        delete a; // Так как деструктор виртуальный, для каждого 
                  // объекта вызовется деструктор его класса
    }
    return 0;
}
Вывод:
This animal moves in some way
Wolf eats meat!
This animal moves in some way
Animal ate something!

Пример аналога виртуальных функций в PHP

Аналогом в PHP можно считать использование позднего статического связывания.

class A {
    public static function a() {
        return "вода";
    }
    public function __construct() {
        echo static::a() . "\n"; // позднее статическое связывание
    }
}

class B extends A {
    public static function a() {
        return "огонь";
    }
}

new A(); // вода
new B(); // огонь

Пример виртуальной функции в Delphi

Язык Object Pascal, использующийся в Delphi, тоже поддерживает полиморфизм. Рассмотрим пример:

Объявим два класса. Предка (Ancestor):

 TAncestor = class
 private
 protected
 public
   {Виртуальная процедура.} 
   procedure VirtualProcedure; virtual; 
   procedure StaticProcedure;
 end;

и его потомка (Descendant):

 TDescendant = class(TAncestor)
 private
 protected
 public
    {Перекрытие виртуальной процедуры.}
   procedure VirtualProcedure; override;
   procedure StaticProcedure;
 end;

Как видно в классе предке объявлена виртуальная функция — VirtualProcedure. Чтобы воспользоваться достоинствами полиморфизма, её нужно перекрыть в потомке.

Реализация выглядит следующим образом:

 { TAncestor }
   
 procedure TAncestor.StaticProcedure;
 begin
   ShowMessage('Ancestor static procedure.');
 end;
   
 procedure TAncestor.VirtualProcedure;
 begin
   ShowMessage('Ancestor virtual procedure.');
 end;
 { TDescendant }
   
 procedure TDescendant.StaticProcedure;
 begin
   ShowMessage('Descendant static procedure.');
 end;
   
 procedure TDescendant.VirtualProcedure;
 begin
   ShowMessage('Descendant override procedure.');
 end;

Посмотрим как это работает:

 procedure TForm2.BitBtn1Click(Sender: TObject);
 var
   MyObject1: TAncestor;
   MyObject2: TAncestor;
 begin
   MyObject1 := TAncestor.Create;
   MyObject2 := TDescendant.Create;
   try
     MyObject1.StaticProcedure;
     MyObject1.VirtualProcedure;
     MyObject2.StaticProcedure;
     MyObject2.VirtualProcedure;
   finally
     MyObject1.Free;
     MyObject2.Free;
   end;
 end;

Заметьте, что в разделе var мы объявили два объекта MyObject1 и MyObject2 типа TAncestor. А при создании MyObject1 создали как TAncestor, а MyObject2 как TDescendant. Вот что мы увидим при нажатии на кнопку BitBtn1:

  1. Ancestor static procedure.
  2. Ancestor virtual procedure.
  3. Ancestor static procedure.
  4. Descendant override procedure.

Для MyObject1 все понятно, просто вызвались указанные процедуры. А вот для MyObject2 это не так.

Вызов MyObject2.StaticProcedure; привел к появлению «Ancestor static procedure.». Ведь мы объявили MyObject2: TAncestor, поэтому и была вызвана процедура StaticProcedure; класса TAncestor.

А вот вызов MyObject2.VirtualProcedure; привел к вызову VirtualProcedure; реализованной в потомке(TDescendant). Это произошло потому, что MyObject2 был создан не как TAncestor, а как TDescendant: MyObject2 := TDescendant.Create; . И виртуальный метод VirtualProcedure был перекрыт.

В Delphi полиморфизм реализован с помощью так называемой виртуальной таблицы методов (или VMT).

Достаточно часто виртуальные методы забывают перекрыть с помощью ключевого слова override. Это приводит к закрытию метода. В этом случае замещения методов в VMT не произойдет и требуемая функциональность не будет получена.

Эта ошибка отслеживается компилятором, который выдаёт соответствующее предупреждение.

Пример виртуального метода на C#

Пример виртуального метода на C#. В примере используется ключевое слово base, предоставляющая доступ к методу a() родительского (базового) класса A.
class Program
    {
        static void Main(string[] args)
        {
            A myObj = new B();
            Console.ReadKey();
        }        
    }

    //Базовый класс A
    public class A
    {
        public virtual string a()
        {
            return "огонь";
        }
    }

    //Произвольный класс B наследующий класс A
    class B : A
    {
        public override string a()
        {
            return "вода";
        }

        public B()
        {
            //Выводим результат возвращаемый переопределенным методом
            Console.Out.WriteLine(a()); //вода
            //Выводим результат возвращаемый методом родительского класса
            Console.Out.WriteLine(base.a());    //огонь
        }
    }

Вызов метода предка из перекрытого метода

Бывает необходимо вызвать метод предка в перекрытом методе.

Объявим два класса. Предка(Ancestor):

 TAncestor = class
 private
 protected
 public
   {Виртуальная процедура.} 
   procedure VirtualProcedure; virtual; 
 end;

и его потомка (Descendant):

 TDescendant = class(TAncestor)
 private
 protected
 public
    {Перекрытие виртуальной процедуры.}
   procedure VirtualProcedure; override;
 end;

Обращение к методу предка реализуется с помощью ключевого слова «inherited»

 procedure TDescendant.VirtualProcedure;
 begin
     inherited;
 end;

Стоит помнить, что в Delphi деструктор должен быть обязательно перекрытым — «override» — и содержать вызов деструктора предка

TDescendant = class(TAncestor)
 private
 protected
 public
    destructor Destroy; override;
 end;
 destructor TDescendant. Destroy;
 begin
     inherited;
 end;

В языке C++ не нужно вызывать конструктор и деструктор предка, деструктор должен быть виртуальным. Деструкторы предков вызовутся автоматически. Чтобы вызвать метод предка, нужно явно вызвать метод:

class Ancestor
{
public:
  virtual void  function1 () { printf("Ancestor::function1"); }
};

class Descendant : public Ancestor
{
public:
  virtual void  function1 () {
     printf("Descendant::function1");
     Ancestor::function1(); // здесь будет напечатано "Ancestor::function1"
  }
};

Для вызова конструктора предка нужно указать конструктор:

class Descendant : public Ancestor
{
public:
  Descendant(): Ancestor(){}
};


Ещё примеры

Первый пример
class Ancestor
{
public:
  virtual void  function1 () { cout << "Ancestor::function1()" << endl; }
  void          function2 () { cout << "Ancestor::function2()" << endl; }
};

class Descendant : public Ancestor
{
public:
  virtual void  function1 () { cout << "Descendant::function1()" << endl; }
  void          function2 () { cout << "Descendant::function2()" << endl; }
};

Descendant*  pointer      = new Descendant ();
Ancestor*    pointer_copy = pointer;

pointer->function1 ();
pointer->function2 ();

pointer_copy->function1 ();
pointer_copy->function2 ();

В этом примере класс Ancestor определяет две функции, одну из них виртуальную, другую — нет. Класс Descendant переопределяет обе функции. Однако, казалось бы одинаковое обращение к функциям даёт разные результаты. На выводе программа даст следующее:

    Descendant::function1()
    Descendant::function2()
    Descendant::function1()
    Ancestor::function2()

То есть, для определения реализации виртуальной функции используется информация о типе объекта и вызывается «правильная» реализация, независимо от типа указателя. При вызове невиртуальной функции, компилятор руководствуется типом указателя или ссылки, поэтому вызываются две разные реализации function2(), несмотря на то, что используется один и тот же объект.

Следует отметить, что в С++ можно, при необходимости, указать конкретную реализацию виртуальной функции, фактически вызывая её невиртуально:

pointer->Ancestor::function1 ();

для нашего примера выведет Ancestor::function1(), игнорируя тип объекта.

Второй пример
class A
{
public:
     virtual int function () {
        return 1;
     }
     
     int get() {
         return this->function();
     }
};
 
class B: public A
{
public:
     int function() {
        return 2;
     }
};

#include <iostream>

int main() {
   B b; 
 
   std::cout << b.get() << std::endl; // 2
   
   return 0;
}

Несмотря на то, что в классе B отсутствует метод get(), его можно позаимствовать у класса A, при этом результат работы этого метода вернет вычисления для B::function()!

Третий пример
#include <iostream>
using namespace std;

struct IBase
{
	virtual void foo(int n=1) const = 0;
	virtual ~IBase() = 0;
};

void IBase::foo(int n) const {
	cout << n << " foo\n";
} 

IBase::~IBase() {
	cout << "Base destructor\n";
}

struct Derived final : IBase
{
	virtual void foo(int n=2) const override final {
		IBase::foo(n);
	}
};

void bar(const IBase& arg)
{
	arg.foo();
}

int main () {
	bar(Derived());
	return 0;
}

В данном примере показан пример создания интерфейса IBase. На примере интерфейса показана возможность создания абстрактного класса, не имеющего виртуальных методов: при объявлении деструктора чисто виртуальным и вынесении его определения за тело класса пропадает возможность создавать объекты такого класса, но остаётся возможность создавать потомки данного предка.

Вывод программы будет следующим: 1 foo\nBase destructor\n. Как мы видим, значение аргумента по-умолчанию взялось от типа ссылки, а не от реального типа объекта. Также как и деструктор.

Ключевое слово final показывает, что класс или метод нельзя переопределять, а override, что виртуальный метод явно переопределён.

См. также

Напишите отзыв о статье "Виртуальный метод"

Ссылки

  • [www.parashift.com/c++-faq-lite/virtual-functions.html C++ FAQ Lite: Виртуальные функции в C++]  (англ.)

Отрывок, характеризующий Виртуальный метод

Петя при выезде из Москвы, оставив своих родных, присоединился к своему полку и скоро после этого был взят ординарцем к генералу, командовавшему большим отрядом. Со времени своего производства в офицеры, и в особенности с поступления в действующую армию, где он участвовал в Вяземском сражении, Петя находился в постоянно счастливо возбужденном состоянии радости на то, что он большой, и в постоянно восторженной поспешности не пропустить какого нибудь случая настоящего геройства. Он был очень счастлив тем, что он видел и испытал в армии, но вместе с тем ему все казалось, что там, где его нет, там то теперь и совершается самое настоящее, геройское. И он торопился поспеть туда, где его не было.
Когда 21 го октября его генерал выразил желание послать кого нибудь в отряд Денисова, Петя так жалостно просил, чтобы послать его, что генерал не мог отказать. Но, отправляя его, генерал, поминая безумный поступок Пети в Вяземском сражении, где Петя, вместо того чтобы ехать дорогой туда, куда он был послан, поскакал в цепь под огонь французов и выстрелил там два раза из своего пистолета, – отправляя его, генерал именно запретил Пете участвовать в каких бы то ни было действиях Денисова. От этого то Петя покраснел и смешался, когда Денисов спросил, можно ли ему остаться. До выезда на опушку леса Петя считал, что ему надобно, строго исполняя свой долг, сейчас же вернуться. Но когда он увидал французов, увидал Тихона, узнал, что в ночь непременно атакуют, он, с быстротою переходов молодых людей от одного взгляда к другому, решил сам с собою, что генерал его, которого он до сих пор очень уважал, – дрянь, немец, что Денисов герой, и эсаул герой, и что Тихон герой, и что ему было бы стыдно уехать от них в трудную минуту.
Уже смеркалось, когда Денисов с Петей и эсаулом подъехали к караулке. В полутьме виднелись лошади в седлах, казаки, гусары, прилаживавшие шалашики на поляне и (чтобы не видели дыма французы) разводившие красневший огонь в лесном овраге. В сенях маленькой избушки казак, засучив рукава, рубил баранину. В самой избе были три офицера из партии Денисова, устроивавшие стол из двери. Петя снял, отдав сушить, свое мокрое платье и тотчас принялся содействовать офицерам в устройстве обеденного стола.
Через десять минут был готов стол, покрытый салфеткой. На столе была водка, ром в фляжке, белый хлеб и жареная баранина с солью.
Сидя вместе с офицерами за столом и разрывая руками, по которым текло сало, жирную душистую баранину, Петя находился в восторженном детском состоянии нежной любви ко всем людям и вследствие того уверенности в такой же любви к себе других людей.
– Так что же вы думаете, Василий Федорович, – обратился он к Денисову, – ничего, что я с вами останусь на денек? – И, не дожидаясь ответа, он сам отвечал себе: – Ведь мне велено узнать, ну вот я и узнаю… Только вы меня пустите в самую… в главную. Мне не нужно наград… А мне хочется… – Петя стиснул зубы и оглянулся, подергивая кверху поднятой головой и размахивая рукой.
– В самую главную… – повторил Денисов, улыбаясь.
– Только уж, пожалуйста, мне дайте команду совсем, чтобы я командовал, – продолжал Петя, – ну что вам стоит? Ах, вам ножик? – обратился он к офицеру, хотевшему отрезать баранины. И он подал свой складной ножик.
Офицер похвалил ножик.
– Возьмите, пожалуйста, себе. У меня много таких… – покраснев, сказал Петя. – Батюшки! Я и забыл совсем, – вдруг вскрикнул он. – У меня изюм чудесный, знаете, такой, без косточек. У нас маркитант новый – и такие прекрасные вещи. Я купил десять фунтов. Я привык что нибудь сладкое. Хотите?.. – И Петя побежал в сени к своему казаку, принес торбы, в которых было фунтов пять изюму. – Кушайте, господа, кушайте.
– А то не нужно ли вам кофейник? – обратился он к эсаулу. – Я у нашего маркитанта купил, чудесный! У него прекрасные вещи. И он честный очень. Это главное. Я вам пришлю непременно. А может быть еще, у вас вышли, обились кремни, – ведь это бывает. Я взял с собою, у меня вот тут… – он показал на торбы, – сто кремней. Я очень дешево купил. Возьмите, пожалуйста, сколько нужно, а то и все… – И вдруг, испугавшись, не заврался ли он, Петя остановился и покраснел.
Он стал вспоминать, не сделал ли он еще каких нибудь глупостей. И, перебирая воспоминания нынешнего дня, воспоминание о французе барабанщике представилось ему. «Нам то отлично, а ему каково? Куда его дели? Покормили ли его? Не обидели ли?» – подумал он. Но заметив, что он заврался о кремнях, он теперь боялся.
«Спросить бы можно, – думал он, – да скажут: сам мальчик и мальчика пожалел. Я им покажу завтра, какой я мальчик! Стыдно будет, если я спрошу? – думал Петя. – Ну, да все равно!» – и тотчас же, покраснев и испуганно глядя на офицеров, не будет ли в их лицах насмешки, он сказал:
– А можно позвать этого мальчика, что взяли в плен? дать ему чего нибудь поесть… может…
– Да, жалкий мальчишка, – сказал Денисов, видимо, не найдя ничего стыдного в этом напоминании. – Позвать его сюда. Vincent Bosse его зовут. Позвать.
– Я позову, – сказал Петя.
– Позови, позови. Жалкий мальчишка, – повторил Денисов.
Петя стоял у двери, когда Денисов сказал это. Петя пролез между офицерами и близко подошел к Денисову.
– Позвольте вас поцеловать, голубчик, – сказал он. – Ах, как отлично! как хорошо! – И, поцеловав Денисова, он побежал на двор.
– Bosse! Vincent! – прокричал Петя, остановясь у двери.
– Вам кого, сударь, надо? – сказал голос из темноты. Петя отвечал, что того мальчика француза, которого взяли нынче.
– А! Весеннего? – сказал казак.
Имя его Vincent уже переделали: казаки – в Весеннего, а мужики и солдаты – в Висеню. В обеих переделках это напоминание о весне сходилось с представлением о молоденьком мальчике.
– Он там у костра грелся. Эй, Висеня! Висеня! Весенний! – послышались в темноте передающиеся голоса и смех.
– А мальчонок шустрый, – сказал гусар, стоявший подле Пети. – Мы его покормили давеча. Страсть голодный был!
В темноте послышались шаги и, шлепая босыми ногами по грязи, барабанщик подошел к двери.
– Ah, c'est vous! – сказал Петя. – Voulez vous manger? N'ayez pas peur, on ne vous fera pas de mal, – прибавил он, робко и ласково дотрогиваясь до его руки. – Entrez, entrez. [Ах, это вы! Хотите есть? Не бойтесь, вам ничего не сделают. Войдите, войдите.]
– Merci, monsieur, [Благодарю, господин.] – отвечал барабанщик дрожащим, почти детским голосом и стал обтирать о порог свои грязные ноги. Пете многое хотелось сказать барабанщику, но он не смел. Он, переминаясь, стоял подле него в сенях. Потом в темноте взял его за руку и пожал ее.
– Entrez, entrez, – повторил он только нежным шепотом.
«Ах, что бы мне ему сделать!» – проговорил сам с собою Петя и, отворив дверь, пропустил мимо себя мальчика.
Когда барабанщик вошел в избушку, Петя сел подальше от него, считая для себя унизительным обращать на него внимание. Он только ощупывал в кармане деньги и был в сомненье, не стыдно ли будет дать их барабанщику.


От барабанщика, которому по приказанию Денисова дали водки, баранины и которого Денисов велел одеть в русский кафтан, с тем, чтобы, не отсылая с пленными, оставить его при партии, внимание Пети было отвлечено приездом Долохова. Петя в армии слышал много рассказов про необычайные храбрость и жестокость Долохова с французами, и потому с тех пор, как Долохов вошел в избу, Петя, не спуская глаз, смотрел на него и все больше подбадривался, подергивая поднятой головой, с тем чтобы не быть недостойным даже и такого общества, как Долохов.
Наружность Долохова странно поразила Петю своей простотой.
Денисов одевался в чекмень, носил бороду и на груди образ Николая чудотворца и в манере говорить, во всех приемах выказывал особенность своего положения. Долохов же, напротив, прежде, в Москве, носивший персидский костюм, теперь имел вид самого чопорного гвардейского офицера. Лицо его было чисто выбрито, одет он был в гвардейский ваточный сюртук с Георгием в петлице и в прямо надетой простой фуражке. Он снял в углу мокрую бурку и, подойдя к Денисову, не здороваясь ни с кем, тотчас же стал расспрашивать о деле. Денисов рассказывал ему про замыслы, которые имели на их транспорт большие отряды, и про присылку Пети, и про то, как он отвечал обоим генералам. Потом Денисов рассказал все, что он знал про положение французского отряда.
– Это так, но надо знать, какие и сколько войск, – сказал Долохов, – надо будет съездить. Не зная верно, сколько их, пускаться в дело нельзя. Я люблю аккуратно дело делать. Вот, не хочет ли кто из господ съездить со мной в их лагерь. У меня мундиры с собою.
– Я, я… я поеду с вами! – вскрикнул Петя.
– Совсем и тебе не нужно ездить, – сказал Денисов, обращаясь к Долохову, – а уж его я ни за что не пущу.
– Вот прекрасно! – вскрикнул Петя, – отчего же мне не ехать?..
– Да оттого, что незачем.
– Ну, уж вы меня извините, потому что… потому что… я поеду, вот и все. Вы возьмете меня? – обратился он к Долохову.
– Отчего ж… – рассеянно отвечал Долохов, вглядываясь в лицо французского барабанщика.
– Давно у тебя молодчик этот? – спросил он у Денисова.
– Нынче взяли, да ничего не знает. Я оставил его пг'и себе.
– Ну, а остальных ты куда деваешь? – сказал Долохов.
– Как куда? Отсылаю под г'асписки! – вдруг покраснев, вскрикнул Денисов. – И смело скажу, что на моей совести нет ни одного человека. Разве тебе тг'удно отослать тг'идцать ли, тг'иста ли человек под конвоем в гог'од, чем маг'ать, я пг'ямо скажу, честь солдата.
– Вот молоденькому графчику в шестнадцать лет говорить эти любезности прилично, – с холодной усмешкой сказал Долохов, – а тебе то уж это оставить пора.
– Что ж, я ничего не говорю, я только говорю, что я непременно поеду с вами, – робко сказал Петя.
– А нам с тобой пора, брат, бросить эти любезности, – продолжал Долохов, как будто он находил особенное удовольствие говорить об этом предмете, раздражавшем Денисова. – Ну этого ты зачем взял к себе? – сказал он, покачивая головой. – Затем, что тебе его жалко? Ведь мы знаем эти твои расписки. Ты пошлешь их сто человек, а придут тридцать. Помрут с голоду или побьют. Так не все ли равно их и не брать?
Эсаул, щуря светлые глаза, одобрительно кивал головой.
– Это все г'авно, тут Рассуждать нечего. Я на свою душу взять не хочу. Ты говог'ишь – помг'ут. Ну, хог'ошо. Только бы не от меня.
Долохов засмеялся.
– Кто же им не велел меня двадцать раз поймать? А ведь поймают – меня и тебя, с твоим рыцарством, все равно на осинку. – Он помолчал. – Однако надо дело делать. Послать моего казака с вьюком! У меня два французских мундира. Что ж, едем со мной? – спросил он у Пети.
– Я? Да, да, непременно, – покраснев почти до слез, вскрикнул Петя, взглядывая на Денисова.
Опять в то время, как Долохов заспорил с Денисовым о том, что надо делать с пленными, Петя почувствовал неловкость и торопливость; но опять не успел понять хорошенько того, о чем они говорили. «Ежели так думают большие, известные, стало быть, так надо, стало быть, это хорошо, – думал он. – А главное, надо, чтобы Денисов не смел думать, что я послушаюсь его, что он может мной командовать. Непременно поеду с Долоховым во французский лагерь. Он может, и я могу».
На все убеждения Денисова не ездить Петя отвечал, что он тоже привык все делать аккуратно, а не наобум Лазаря, и что он об опасности себе никогда не думает.
– Потому что, – согласитесь сами, – если не знать верно, сколько там, от этого зависит жизнь, может быть, сотен, а тут мы одни, и потом мне очень этого хочется, и непременно, непременно поеду, вы уж меня не удержите, – говорил он, – только хуже будет…