Подтвердить что ты не робот

Неявно связанное неявное преобразование

Неявное преобразование может быть действительно полезным, если типы семантически эквивалентны. Например, представьте две библиотеки, которые реализуют тип одинаково, но в разных пространствах имен. Или просто тип, который в основном идентичен, за исключением некоторого семантического сахара здесь и там. Теперь вы не можете передать один тип в функцию (в одной из этих библиотек), которая была предназначена для использования другой, если эта функция не является шаблоном. Если это не так, вам нужно каким-то образом преобразовать один тип в другой. Это должно быть тривиальным (или иначе типы не так идентичны после всех!), Но вызов преобразования явно раздувает ваш код, в основном бессмысленные вызовы функций. Хотя такие функции преобразования могут фактически копировать некоторые значения вокруг, они практически ничего не делают с точки зрения "программистов" высокого уровня.

Неявные конструкторы преобразования и операторы, очевидно, могли бы помочь, но они вводят связь, так что один из этих типов должен знать о другом. Обычно, по крайней мере, при работе с библиотеками, это не так, потому что наличие одного из этих типов делает другой избыточным. Кроме того, вы не всегда можете изменять библиотеки.

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

  • Первым будет предоставление прокси-типа, который реализует операторы преобразования и преобразования-конструкторы (и назначения) для всех задействованных типов и всегда их используют.

  • Второй требует минимального изменения в библиотеках, но обеспечивает большую гибкость: Добавьте конструктор преобразования для каждого задействованного типа, который может быть необязательно включен снаружи.

Например, для типа A добавьте конструктор:

template <class T> A(
  const T& src,
  typename boost::enable_if<conversion_enabled<T,A>>::type* ignore=0
)
{
  *this = convert(src);
}

и шаблон

template <class X, class Y>
struct conversion_enabled : public boost::mpl::false_ {};

который по умолчанию отключает неявное преобразование.

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

template <> struct conversion_enabled<OtherA, A> : public boost::mpl::true_ {};

и реализовать функцию convert, которая может быть найдена через ADL.

Я бы предпочел использовать второй вариант, если у него нет веских аргументов.

Теперь к актуальному вопросу (-ам): Каков предпочтительный способ связывания типов для неявного преобразования? Являются ли мои предложения хорошими идеями? Есть ли недостатки для обоих? Позволяет ли такое превращение так опасно? Если разработчики библиотек в целом поставляют второй метод, когда вероятность того, что их тип будет реплицироваться в программном обеспечении, скорее всего, будет использоваться (я думаю о средстве рендеринга 3d-рендеринга здесь, где большинство этих пакетов реализует 3D-рендеринг вектор).

4b9b3361

Ответ 1

Я бы предпочел ваш "прокси" подход по сравнению с другими вариантами, если бы я побеспокоился об этом вообще.

Правда в том, что я обнаружил, что это такая серьезная проблема во всех сферах развития, что я стараюсь избегать использования какой-либо специфичной для библиотеки конструкции вне моего взаимодействия с этой конкретной библиотекой. Одним из примеров может быть обработка событий/сигналов в разных библиотеках. Я уже выбрал boost как нечто, что является неотъемлемой частью моего собственного кода проекта, поэтому я целенаправленно использую boost:: signals2 для всех сообщений в своем собственном коде проекта. Затем я пишу интерфейсы в библиотеку пользовательского интерфейса, которую я использую.

Другим примером является строка. Каждая проклятая библиотека пользовательского интерфейса создает новую строку. Все мои модели и код данных используют стандартные версии, и я предоставляю интерфейсы для своих оболочек UI, которые работают в таких типах... преобразование в конкретную версию пользовательского интерфейса только в тот момент, когда я напрямую взаимодействую с компонентом пользовательского интерфейса.

Это означает, что я не могу использовать большую мощность, предоставляемую различными независимыми, но схожими конструкциями, и я пишу много дополнительного кода, чтобы справиться с этими преобразованиями, но это того стоит, потому что, если я нахожу лучше библиотеки и/или нужно переключать платформы, становится намного проще сделать это, так как я не позволял этим вещам пробивать себе дорогу во всем.

В принципе, я бы предпочел прокси-подход, потому что я уже это делаю. Я работаю в абстрактных слоях, которые меня удаляют от какой-либо конкретной библиотеки, которую я использую, и подкласса этих абстракций со спецификой, необходимой для взаимодействия с указанной библиотекой. Я ВСЕГДА это делаю, поэтому интересно узнать о какой-то небольшой области, где я хочу обмениваться информацией между двумя сторонними библиотеками, в основном уже ответил.

Ответ 2

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

Недостатком является то, что вы должны обернуть параметр во всех вызовах. Сделано правильно, компилятор даже включит полный вызов без создания экземпляра прокси. И нет никакой связи между классами. Только классы Proxy должны знать их.

Прошло некоторое время с тех пор, как я запрограммировал С++, но прокси-сервер мог бы быть примерно таким:

class Proxy { 
  private:
    IncompatibleType1 *type1;
    IncompatibleType2 *type2;
    //TODO static conversion methods
  public:
    Proxy(IncompatibleType1 *type1) {
      this.type1=type1;
    }
    Proxy(IncompatibleType2 *type2) {
      this.type2=type2;
    }
    operator IncompatibleType1 * () { 
      if(this.type1!=NULL)
        return this.type1;
      else
        return convert(this.type2);
    }
    operator IncompatibleType2 * () { 
      if(this.type2!=NULL)
        return this.type2;
      else
        return convert(this.type1);
    }
}

Вызовы всегда будут выглядеть так:

expectsType1(Proxy(type2));
expectsType1(Proxy(type1));
expectsType2(Proxy(type1));

Ответ 3

Есть ли недостатки для обоих? Позволяет ли такое превращение так опасно? Если разработчики библиотек в целом поставляют второй метод, когда...

В целом, есть недостаток в неявном преобразовании, который делает любую работу в том, что он вредит тем пользователям библиотек, которые чувствительны к скорости (например, используют его - возможно, не знают об этом - во внутреннем цикле). Это может также вызвать неожиданное поведение, когда доступно несколько разных неявных преобразований. Поэтому я бы сказал, что это было бы плохим советом для разработчиков библиотек в целом, чтобы позволить неявные преобразования.

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

Общий механизм, подобный тому, который вы предлагаете со вторым вариантом, редко бывал полезным, и облегчил бы сделать некоторые довольно плохие вещи. Возьмите это для примера (изобретенный, но все же):

struct A {
    A(float x) : x(x) {}
    int x;
};

struct B {
    B(int y): y(y) {}
    template<class T> B(const T &t) { *this = convert(t); }
    int y;
};

inline B convert(const A &a) {
    return B(a.x+1);
}

В этом случае отключение конструктора шаблонов изменит значение B (20.0). Другими словами, просто добавив неявный конструктор преобразования, вы можете изменить интерпретацию существующего кода. Очевидно, это очень опасно. Таким образом, неявное преобразование не должно быть общедоступным, а скорее обеспечено для очень специфических типов, только когда оно ценно и понятно. Это не было бы достаточно распространено, чтобы гарантировать ваш второй вариант.

Подводя итог: это лучше сделать за пределами библиотек, с полным пониманием всех типов, которые будут поддерживаться. Прокси-объект кажется идеальным.

Ответ 4

Относительно вашего первого варианта:

Предоставить прокси-тип, который реализует операторы преобразования и преобразования-конструкторы (и заданий) для всех вовлеченных типы и всегда используйте это.

Вы можете использовать строки (текст) в качестве прокси-сервера, если производительность не является критичной (или, может быть, если это так, и данные в корне строят в любом случае). Реализуйте операторы << и >>, и вы можете использовать boost::lexical_cast<> для преобразования, используя текстовое промежуточное представление:

const TargetType& foo = lexical_cast<TargetType>(bar);

Очевидно, что если вы очень обеспокоены производительностью, вы не должны этого делать, и есть и другие оговорки (оба типа должны иметь разумные текстовые представления), но он довольно универсален и "просто работает" с множеством существующих вещей.

Ответ 5

Можно ли использовать перегрузку оператора конверсии? как в следующем примере:

class Vector1 {
  int x,y,z;
public:
  Vector1(int x, int y, int z) : x(x), y(y), z(z) {}
};

class Vector2 {
  float x,y,z;
public:
  Vector2(float x, float y, float z) : x(x), y(y), z(z) {}

  operator Vector1()  {
    return Vector1(x, y, z);
  }
};

Теперь эти вызовы преуспевают:

void doIt1(const Vector1 &v) {
}

void doIt2(const Vector2 &v) {
}

Vector1 v1(1,2,3);
Vector2 v2(3,4,5);
doIt1(v1);
doIt2(v2);

doIt1(v2); // Implicitely convert Vector2 into Vector1

Ответ 6

Сегодня я медлителен. В чем проблема с повторным использованием прокси-шаблона? Мой совет, не тратьте много времени на то, чтобы беспокоиться о том, что функции копирования выполняют ненужную работу. Кроме того, явное - это хорошо.