C++/CLI: как перегрузить оператор для принятия ссылочных типов?

Я пытаюсь создать класс значений CLI c_Location с перегруженными операторами, но я думаю, что у меня проблема с боксом. Я реализовал перегрузку операторов, как показано во многих руководствах, поэтому я уверен, что это должно быть правильно. Это мой код:

value class c_Location
{
public:
  double x, y, z;
  c_Location (double i_x, double i_y, double i_z) : x(i_x), y(i_y), z(i_z) {}

  c_Location& operator+= (const c_Location& i_locValue)
  {
    x += i_locValue.x;
    y += i_locValue.y;
    z += i_locValue.z;
    return *this;
  }
  c_Location operator+ (const c_Location& i_locValue)
  {
    c_Location locValue(x, y, z);
    return locValue += i_locValue;
  }
};

int main()
{
  array<c_Location,1>^ alocData = gcnew array<c_Location,1>(2);
  c_Location locValue, locValue1, locValue2;
  locValue = locValue1 + locValue2;
  locValue = alocData[0] + alocData[1];  // Error C2679 Binary '+': no operator found which takes a right-hand operand of type 'c_Location'
}

После более длительного поиска я обнаружил, что ошибка возникает из-за того, что операнд является ссылочным типом, поскольку он является элементом массива типа значения, а функция принимает только типы значений, поскольку она принимает неуправляемую ссылку. Теперь у меня есть 2 возможности:

  1. добавление приведения распаковки к c_Location и, таким образом, изменение ошибочной строки в main() на
    locValue = alocData[0] + (c_Location)alocData[1];
  2. изменение перегрузки оператора +, чтобы он принимал параметр по значению, а не по ссылке:
    c_Location operator+ (const c_Location i_locValue)

оба варианта работают, но, насколько я вижу, у них обоих есть недостатки:
вариант 1 означает, что я должен явно выполнять приведение типов везде, где это необходимо.
вариант 2 означает, что функция создаст копию параметра на своем звоните и, следовательно, тратьте впустую производительность (хотя и не так много).

Мои вопросы: Верен ли вообще мой анализ сбоев или у сбоя есть другая причина?
Есть ли лучшая третья альтернатива?
Если нет: какой вариант, 1 или 2, лучше? Сейчас я предпочитаю №2.


person Tobias Knauss    schedule 02.08.2013    source источник


Ответы (2)


Версия TL;DR:

Для управляемого кода используйте % для параметра передачи по ссылке, а не &


Ваш диагноз не совсем верный. Бокс не имеет ничего общего с вашей проблемой. Но в каком-то смысле ссылочные типы делают это.

Вы были очень близки, когда сказали: «Я обнаружил, что ошибка возникает из-за того, что операнд является ссылочным типом». Ну, операнд - это тип значения, а не ссылочный тип. Но ошибка возникает, когда операнд хранится внутри ссылочного типа, потому что тогда он находится внутри кучи со сборкой мусора (куда помещаются все экземпляры ссылочных типов). Это относится как к массивам, так и к вашим собственным объектам, которые содержат элемент типа значения.

Опасность заключается в том, что когда работает сборщик мусора, он может перемещать элементы в куче gc. И это ломает нативные указатели (*) и ссылки (&), потому что они сохраняют адрес и ожидают, что он останется неизменным навсегда. Чтобы справиться с этой проблемой, C++/CLI предоставляет указатели отслеживания (^) и ссылки отслеживания (%), которые работают вместе со сборщиком мусора для выполнения двух задач:

  • убедитесь, что окружающий объект не освобождается, пока вы его используете
  • найти новый адрес, если сборщик мусора перемещает окружающий объект

Для использования из C++/CLI вы можете сделать operator+ не членом, как обычный C++.

value class c_Location
{
public:
    double x, y, z;
    c_Location (double i_x, double i_y, double i_z) : x(i_x), y(i_y), z(i_z) {}

    c_Location% operator+= (const c_Location% i_locValue)
    {
        x += i_locValue.x;
        y += i_locValue.y;
        z += i_locValue.z;
        return *this;
    }
};

c_Location operator+ (c_Location left, const c_Location% right)
{
    return left += right;
}

Недостатком является то, что C# не будет использовать не-членов, для совместимости с C# напишите его как оператор, не являющийся членом (с двумя явными операндами), но сделайте его общедоступным статическим членом.

value class c_Location
{
public:
    double x, y, z;
    c_Location (double i_x, double i_y, double i_z) : x(i_x), y(i_y), z(i_z) {}

    c_Location% operator+= (const c_Location% i_locValue)
    {
        x += i_locValue.x;
        y += i_locValue.y;
        z += i_locValue.z;
        return *this;
    }

    static c_Location operator+ (c_Location left, const c_Location% right)
    {
        return left += right;
    }
};

Нет причин беспокоиться об этом для operator+=, поскольку C# все равно этого не распознает, он будет использовать operator+ и присваивать результат обратно исходному объекту.


Для примитивных типов, таких как double или int, вы можете обнаружить, что вам нужно также использовать %, но только если вам нужна ссылка на экземпляр этого примитивного типа, хранящийся внутри управляемого объекта:

double d;
array<double>^ a = gcnew darray<double>(5);
double& native_ref = d; // ok, d is stored on stack and cannot move
double& native_ref2 = a[0]; // error, a[0] is in the managed heap, you MUST coordinate with the garbage collector
double% tracking_ref = d; // ok, tracking references with with variables that don't move, too
double% tracking_ref2 = a[0]; // ok, now you and the garbage collector are working together
person Ben Voigt    schedule 02.08.2013
comment
Это работает, спасибо! Но теперь я немного запутался: означает ли это, что я буду использовать ссылку на отслеживание, хотя я не создавал gc-объекты? И должен ли я также использовать управляемую ссылку для неуправляемых типов?: c_Location& operator+= (const double% i_dValue) ? Или я совершенно не прав, и все типы будут скомпилированы как управляемые типы (double --> System::Double?)? - person Tobias Knauss; 02.08.2013
comment
@Tobias: Позвольте мне немного расширить свой ответ, чтобы охватить ваш первоначальный диагноз, который был близким, но не совсем правильным. - person Ben Voigt; 02.08.2013
comment
Сегодня я сделал этот класс сборкой .net и обнаружил, что больше не могу получить доступ к некоторым операторам, потому что операторы, не являющиеся членами, конечно, не были частью интерфейса сборки. Также меня смутила моя реализация: CLocation& operator+= (const CLocation i_loc)(член), inline CLocation operator+ (CLocation i_loc1, const CLocation i_loc2) которые копируют вместо ссылок. Поэтому я сделал их (статическими) членами и улучшил их: static CLocation operator+ (CLocation i_loc1, const CLocation% i_loc2), CLocation% operator+= (const CLocation% i_loc2). Так должно быть хорошо, не так ли? - person Tobias Knauss; 19.11.2013

Правила несколько отличаются от родного C++:

  • интерфейс командной строки требует, чтобы перегруженные операторы были статическими членами класса
  • вы можете использовать ключевое слово const в C++/CLI, но вы не получите от этого никакой пользы, CLI не поддерживает обеспечение константности, и почти нет других языков .NET, которые его поддерживают.
  • передача значений типа значения должна выполняться по значению, в этом смысл наличия типов значений в .NET в первую очередь. Использование ссылки & очень хлопотно, это собственный указатель во время выполнения, который сборщик мусора не может настроить. Вы получите ошибку компиляции, если попытаетесь использовать перегрузку оператора для c_Location, встроенного в управляемый класс. Если вы хотите избежать семантики копирования значений, вам следует вместо этого объявить ref class. Шляпа^ в вашем коде.
  • любой тип взаимодействия, который вы создаете в C++/CLI, должен быть объявлен public, чтобы его можно было использовать из других сборок и языков .NET. Не совсем ясно, является ли это вашим намерением, обычно это причина, по которой вы пишете код C++/CLI.

Вместо этого вы можете сделать свой класс значений таким:

public value class c_Location
{
public:
  double x, y, z;
  c_Location (double i_x, double i_y, double i_z) : x(i_x), y(i_y), z(i_z) {}

  static c_Location operator+= (c_Location me, c_Location rhs)
  {
    me.x += rhs.x;
    me.y += rhs.y;
    me.z += rhs.z;
    return me;
  }
  static c_Location operator+ (c_Location me, c_Location rhs)
  {
    return c_Location(me.x + rhs.x, me.y + rhs.y, me.z + rhs.z);
  }
};

Не проверено, должно быть близко. Теперь вы увидите, что ваш код в main() компилируется без проблем.

person Hans Passant    schedule 02.08.2013