Рекомендации по реализации постоянных ссылок в 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 ответа:
Если вы хотите что-то, что чувствует себя как 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 };