Лучшие практики для реализации константных ссылок в C ++ / CLI - PullRequest
2 голосов
/ 07 февраля 2011

В нативном 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 поддерживает огромное количество данных (слишком много для копирования), которые не следует изменять извне класса A?

Ответы [ 2 ]

4 голосов
/ 07 февраля 2011

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

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

1 голос
/ 07 февраля 2011

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

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

class B 
{
public:
    int X;

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

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

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
};
...