2016-12-08 29 views
6

Я конвертирую большой код для использования пользовательских общих указателей вместо необработанных указателей. У меня проблема с разрешением перегрузки. Рассмотрим следующий пример:Разрешение перегрузки и общие указатели на const

#include <iostream> 

struct A {}; 

struct B : public A {}; 

void f(const A*) 
{ 
    std::cout << "const version\n"; 
} 

void f(A*) 
{ 
    std::cout << "non-const version\n"; 
} 

int main(int, char**) 
{ 
    B* b; 
    f(b); 
} 

Этот код правильно пишет «неконстантную версию», потому что qualification conversions играют роль в ранжировании последовательностей неявным. Теперь посмотрим на версию с помощью shared_ptr:

#include <iostream> 
#include<memory> 

struct A {}; 

struct B : public A {}; 

void f(std::shared_ptr<const A>) 
{ 
    std::cout << "const version\n"; 
} 

void f(std::shared_ptr<A>) 
{ 
    std::cout << "non-const version\n"; 
} 

int main(int, char**) 
{ 
    std::shared_ptr<B> b; 
    f(b); 
} 

Этот код не компилируется, потому что вызов функции неоднозначно.

Я понимаю, что user-defined deduction-guide будет решением, но он все еще не существует в Visual Studio.

Я преобразовываю код, используя регулярное выражение, потому что тысячи таких вызовов. Регулярные выражения не могут отличить вызовы, которые соответствуют версии const, от тех, которые соответствуют неконстантной версии. Возможно ли более тонкое управление разрешением перегрузки при использовании общих указателей и избежать необходимости менять каждый вызов вручную? Конечно, я мог бы (get) указать (raw) указатель и использовать его в вызове, но я хочу полностью удалить исходные указатели.

+0

@ πάνταῥεῖ Это бесплатная функция перегрузки, не задействована какая-либо функция-член. –

+0

«Конечно, я мог бы .get() использовать необработанный указатель и использовать его в вызове, но я хочу полностью удалить исходные указатели». Выделение исходных указателей не обязательно является лучшим способом. Во многих случаях необработанный указатель будет намного более результативным, чем 'shared_ptr', поскольку контейнер уменьшает доступность места в памяти к этому экземпляру. Если у вас есть время, я бы посоветовал вам ознакомиться с параметром «Параметры смарт-указателя травы Трава Саттера» (https://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/). – cyberbisson

+1

@cyberbisson Вы, конечно, правы, но мне нужны общие указатели, чтобы спасти код из состояния хаоса, который был вызван многими людьми, проходящими много объектов вокруг, используя необработанные указатели, и я готов заплатить небольшую цену за какой-то заказ. Спасибо за ссылку, я передаю общие указатели по постоянной ссылке, когда это возможно. –

ответ

1

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

template <class T> 
void f(std::shared_ptr<T> a) 
{ 
    f(std::static_pointer_cast<A>(a)); 
} 

template <class T> 
void f(std::shared_ptr<const T> a) 
{ 
    f(std::static_pointer_cast<const A>(a)); 
} 

Вы можете также потенциально использовать std::enable_if ограничить первую перегрузку непредставленной constT с, и/или ограничить оба перегрузки до T с полученных от A.

Как это работает:

У вас есть std::shared_ptr<X> для некоторых X, который не является ни A, ни const A (это либо B или const B). Без перегрузки шаблонов компилятор должен выбрать для преобразования этого std::shared_ptr<X> в std::shared_ptr<A> или std::shared_ptr<const A>. Оба являются одинаково хорошими преобразованиями по рангам (оба являются определяемыми пользователем преобразованием), поэтому существует двусмысленность.

С перегруженных шаблонов добавлены, существует четыре типа параметров на выбор (давайте проанализируем X = const B случай):

  1. std::shared_ptr<A>
  2. std::shared_ptr<const A>
  3. std::shared_ptr<const B> экземпляр из первого шаблона, с T = const B.
  4. std::shared_ptr<const B> со вторым шаблоном, с T = B.

Очевидно, что типы 3 и 4 лучше, чем 1 и 2, так как они вообще не требуют преобразования. Поэтому один из них будет выбран.

Типы 3 и 4 идентичны сами по себе, но с разрешением перегрузки шаблонов вводятся дополнительные правила. А именно, шаблон, который является «более специализированным» (в большей степени соответствует несимметричной подписи), предпочтительнее одного менее специализированный. Так как перегрузка 4 имела const в непарной части подписи (за пределами T), она более специализирована и поэтому выбрана.

Не существует правила, в котором говорится, что «шаблоны лучше». На самом деле, это наоборот: когда шаблон и не шаблон имеют одинаковую стоимость, предпочтительным является не шаблон. Фокус здесь в том, что шаблон (ы) имеет lesser стоимость (не требуется преобразования), чем шаблон (требуется определенное преобразование).

+0

Это работает, но я не понимаю ... У нас есть те же две функции, плюс еще два. Есть ли правило, в котором говорится, что функция шаблона лучше соответствует функции, отличной от шаблона? Можете ли вы указать на дальнейшее чтение? –

+0

Теперь я понимаю. Благодаря! –

0

Отправка тега может решить проблему.
вытекает минимальный, рабочий пример:

#include <iostream> 
#include<memory> 

struct A {}; 

struct B : public A {}; 

void f(const A*, std::shared_ptr<const A>) 
{ 
    std::cout << "const version\n"; 
} 

void f(A*, std::shared_ptr<A>) 
{ 
    std::cout << "non-const version\n"; 
} 

template<typename T> 
void f(std::shared_ptr<T> ptr) 
{ 
    f(ptr.get(), ptr); 
} 

int main(int, char**) 
{ 
    std::shared_ptr<B> b; 
    f(b); 
} 

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