Можно ли иметь метательную реализацию swap-члена?

Общая рекомендация при написании классов (с использованием идиомы копирования и подкачки) состоит в том, чтобы предоставить функцию члена подкачки без выбрасывания. (Effective C++, 3-е издание, пункт 25 и другие ресурсы)

Однако что, если я не могу предоставить гарантию nothrow, потому что мой класс использует сторонний член класса, который не обеспечивает операцию обмена?

// Warning: Toy code !!!

class NumberBuffer {
public:
    ...
    void swap(NumberBuffer& rhs);

public:
    float* m_data;
    size_t m_n;
    CString m_desc;
};

void swap(NumberBuffer& lhs, NumberBuffer& rhs) {
    lhs.swap(rhs);
}

void NumberBuffer::swap(NumberBuffer& rhs) {
    using std::swap;
    swap(m_data, rhs.m_data);
    swap(m_n, rhs.m_n);
    swap(m_desc, rhs.m_desc); // could throw if CString IsLocked and out-of-mem
}

Обмен CString не может быть выполнен без броска, поэтому есть вероятность, что обмен может завершиться ошибкой.

Примечание. Для редких сторонних курсов можно использовать интеллектуальный указатель указателя мыши (pimpl), но --

Примечание: CString — хороший пример, так как никто в здравом уме (?) не стал бы удерживать все члены концептуально простого и вездесущего класса, такого как CString, через pimpl (smart ptr), потому что это действительно выглядело бы ужасно — и, с другой стороны, нет (от краткосрочного до среднесрочного) шанса изменить CString, чтобы разрешить полный обмен без выбрасывания.

Итак, нормально ли иметь функцию-член swap, которая может выдать исключение, если вы ничего не можете с этим поделать? (Или вы знаете способы обойти эту головоломку?)

Редактировать: И: Можно ли использовать метательный элемент подкачки с идиомой копирования и подкачки, чтобы обеспечить базовую гарантию, если не сильную гарантию?


person Martin Ba    schedule 05.10.2011    source источник
comment
Не ответ, но если вы сможете отойти от CString, то проблема исчезнет...   -  person David Rodríguez - dribeas    schedule 05.10.2011
comment
Имейте в виду, что по аналогичной теме через довольно небольшое количество лет у вас будет та же проблема, когда вы попытаетесь реализовать перемещение C++ 11 для своего класса. Если член не может быть заменен неразбрасываемым, то, скорее всего, он не будет и неперемещаемым, потому что они очень похожи.   -  person Steve Jessop    schedule 05.10.2011


Ответы (3)


Итак, нормально ли иметь потенциально вызывающую функцию-член swap, если вы не можете с этим поделать? (Или вы знаете способы обойти эту головоломку?)

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

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

person David Rodríguez - dribeas    schedule 05.10.2011
comment
+1 Можете ли вы провести небольшую связь между безопасностью исключений и безопасностью потоков? Или это опечатка? - person Christian Rau; 05.10.2011
comment
@ChristianRau: скорее всего, опечатка, учитывая контекст... на самом деле это настолько очевидно, что я подсознательно прочитал исключение безопасность :) - person Matthieu M.; 05.10.2011
comment
@ChristianRau: это была опечатка, извините за это. Я немного устал :) - person David Rodríguez - dribeas; 05.10.2011
comment
Дэвид, да, никаких строгих гарантий и, насколько я понимаю, также никаких основных гарантий (?), если только вы не позаботитесь об этом. Что вы думаете? Можно ли вообще использовать этот обмен в идиоме копирования и обмена? Или нужно было бы добавить некоторые вещи try-catch-revert, чтобы обеспечить базовую гарантию? (Возможно, вы могли бы отредактировать свой ответ, чтобы включить это.) - person Martin Ba; 05.10.2011
comment
@Martin: вы правы в том, что этот swap нельзя использовать для предоставления какой-либо гарантии исключения. Базовая гарантия может быть реализована путем включения внутрь try/catch, но это в основном то же самое с любым случайным фрагментом кода... - person David Rodríguez - dribeas; 05.10.2011
comment
Я думаю, вы ошибаетесь - идиома копирования и подкачки обеспечивает надежную гарантию исключения, если swap обеспечивает надежную гарантию исключения или лучше. - person JoeG; 05.10.2011
comment
@JoeGauterin: Как это противоречит тому, что я сказал? (о, правильно, swap не обязательно должно быть без броска, только обеспечить сильную гарантию исключения) - person David Rodríguez - dribeas; 05.10.2011
comment
@DavidRodríguez: Да, вы сказали, что без гарантии отсутствия бросков своп нельзя использовать для обеспечения безопасности исключений. - person JoeG; 05.10.2011

В броске swap нет ничего плохого, просто он менее полезен, чем версия без броска.

Идиома копирования и подкачки не требует, чтобы swap не вызывала исключения, чтобы обеспечить строгую гарантию исключения. swap нужно только обеспечить надежную гарантию исключения.

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

person JoeG    schedule 05.10.2011
comment
Отличные очки. без броска против сильного. Если есть только один член, который не предоставляет гарантию отсутствия броска, то это просто вопрос обмена (копирования) этого первого, чтобы получить сильную гарантию. Если участников больше одного, думаю, кому-то не повезло. - person Martin Ba; 05.10.2011

Вы можете легко сделать это nothrow:

void NumberBuffer::swap(NumberBuffer& rhs) throw()
{
    try
    {
        std::swap(m_desc, rhs.m_desc);   //could throw
        std::swap(m_data, rhs.m_data);
        std::swap(m_n, rhs.m_n);
    }
    catch(...)
    {
    }
}

Конечно, это не реальное решение проблемы, но теперь вы, по крайней мере, получили свой невыбрасываемый своп ;)

person Christian Rau    schedule 05.10.2011
comment
За исключением того, что swap не выполняет свой контракт и тихо терпит неудачу! Счастливого времени, находящего эти ошибки! Я предлагаю вам либо квалифицировать свой пример как не делать это, либо удалить ответ. Спасибо! - person Martin Ba; 05.10.2011
comment
@Martin Это не было слишком серьезно, и я понимаю, что комментарий был бы лучше для этого, но не стесняйтесь голосовать против. - person Christian Rau; 05.10.2011
comment
Учитывая спецификацию исключений throw(), было бы лучше без try/catch. Таким образом, компилятор организует вызов std::terminate, если он выдает исключение, по крайней мере, при условии, что вы не находитесь в каком-либо несовместимом режиме MSVC. Все еще может быть не то, что хочет вызывающий, но, по крайней мере, очевидно, что что-то не так. - person Steve Jessop; 05.10.2011
comment
Это не было слишком серьезно Это было довольно очевидно, ИМО. Есть забавная кнопка? (как в /.) - person curiousguy; 07.10.2011