Рекомендации по реализации постоянных ссылок в C++ / CLI


В родном языке C++ часто имеет смысл возвращать объект в виде постоянной ссылки. Рассмотрим класс A, предоставляющий доступ только для чтения к экземпляру класса B:

class B {
public:
  int X;

  B(int x)
    : X(x)
  {}

  B(const B &b) // copy constructor
    : X(b.X)
  {}

};

class A {
private:
  B &b;

public:
  A(int x)
    : b(*new B(x))
  {}

  const B &GetB()
  {
    return b;
  }
};

Теперь у клиента есть выбор, чтобы прочитать B-данные A либо очень эффективно по ссылке, либо создать свою собственную копию, если это необходимо:

A a1(1);
const B &b1 = a1.GetB(); // constant reference
// b1.X = 2; // compilation error

B b2 = a1.GetB(); // through copy constructor
b2.X = 2; // ok, but doesn't affect a1

В любом случае гарантируется, что никто извне не сможет изменить данные в экземпляре члена A из B. Таким образом, это идеальное решение.

Эквивалентный CLI конструкция выглядела бы так на первый взгляд:

public ref class B {
public:
  int X;

  B(int x)
    : X(x)
  {}

};

public ref class A {
private:
  B ^b;

public:
  A(int x)
    : b(gcnew B(x))
  {}

  const B %GetB()
  {
    return *b;
  }
};

Но это не имеет большого смысла, так как он работает только в C++/CLI. Когда вы ссылаетесь на него из другого языка .NET, например C# или VB.NET, вы вообще не увидите реализацию GetB. Хорошо, попробуйте вместо этого:

  const B ^GetB()
  {
    return b;
  }

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

A ^a1 = gcnew A(1);

const B ^b = a1->GetB(); // ok
b->X = 2; // error C3892: you cannot assign to a variable that is const

// error C2440: 'initializing' : cannot convert from 'const B ^' to 'B ^'
B ^b = a1->GetB();

В то же время в другой сборке .NET (даже при использовании C++/CLI) константа теряется. Действительно, следующие работы в пределах второй сборки ссылаются на ту, которая содержит класс A:

A ^a1 = gcnew A(1);
B ^b2 = a1->GetB();
b2->X = 2; // b->X changed within a1
Удивительно, но таким образом вы получаете "больше" доступа к объекту извне сборки, чем изнутри, потому что языковая конструкция ведет себя по-разному. Является ли это намеренным поведением?

В любом случае, как лучше всего преобразовать идею объекта с постоянным возвратом в мир .NET? Как бы вы реализовали класс A в стиле CLR, если бы класс B поддерживал огромное количество данных (слишком много для копирования) что не должно быть изменено вне класса А?

2 2

2 ответа:

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

Кстати ... ваши примеры странны. Первый B имеет бессмысленный конструктор копирования (потому что он ведет себя как компилятор, созданный компилятором), поэтому, возможно, он должен быть:

class B 
{
public:
    int X;

    B( int x )
        : X( x )
    {}
};

Сначала A пропускает свой член b, а геттер не является const, поэтому, возможно, он должен быть:

class A
{
private:
    B b;    
public:
    A( int x )
        : b( x )
    {}

    const B& GetB() const
    {
        return b;
    }
private:
    // block copy and assignment here
    // if that was point of reference member
};

.NET, к сожалению, не имеет понятия о const-корректности. Компилятор C++/CLI прекрасно обрабатывает const на собственных типах, но делает его непригодным для использования с управляемыми типами.

Извините за плохие новости. Я ненавижу это так же сильно, как и ты.