В C++ string_cast<> функция шаблон


В C++, чтобы упростить преобразования строк между СТД::строка и с std::wstring, которая, я создал следующую программу шаблон функции:

#pragma once

#include <vector>
#include <string>
#include <cstring>
#include <cwchar>
#include <cassert>

template<typename Td> 
Td string_cast( const char* pSource, unsigned int codePage = CP_ACP );

template<typename Td> 
Td string_cast( const wchar_t* pSource, unsigned int codePage = 1200 );

template<typename Td> 
Td string_cast( const std::string& source, unsigned int codePage = CP_ACP );

template<typename Td> 
Td string_cast( const std::wstring& source, unsigned int codePage = 1200 );

template<>
std::string string_cast( const char* pSource, unsigned int codePage )
{
    assert( pSource != 0 );
    return std::string( pSource );
}

template<>
std::wstring string_cast( const char* pSource, unsigned int codePage )
{
    assert( pSource != 0 );
    std::size_t sourceLength = std::strlen( pSource );
    if( sourceLength == 0 )
    {
        return std::wstring();
    }

    int length = ::MultiByteToWideChar( codePage, 0, pSource, sourceLength, NULL, 0 );
    if( length == 0 )
    {
        return std::wstring();
    }

    std::vector<wchar_t> buffer( length );
    ::MultiByteToWideChar( codePage, 0, pSource, sourceLength, &buffer[ 0 ], length );

    return std::wstring( buffer.begin(), buffer.end() );
}

template<>
std::string string_cast( const wchar_t* pSource, unsigned int codePage )
{
    assert( pSource != 0 );
    size_t sourceLength = std::wcslen( pSource );
    if( sourceLength == 0 )
    {
        return std::string();
    }

    int length = ::WideCharToMultiByte( codePage, 0, pSource, sourceLength, NULL, 0, NULL, NULL );
    if( length == 0 )
    {
        return std::string();
    }

    std::vector<char> buffer( length );
    ::WideCharToMultiByte( codePage, 0, pSource, sourceLength, &buffer[ 0 ], length, NULL, NULL );

    return std::string( buffer.begin(), buffer.end() );
}

template<>
std::wstring string_cast( const wchar_t* pSource, unsigned int codePage )
{
    assert( pSource != 0 );
    return std::wstring( pSource );
}

template<>
std::string string_cast( const std::string& source, unsigned int codePage )
{
    return source;
}

template<>
std::wstring string_cast( const std::string& source, unsigned int codePage )
{
    if( source.empty() )
    {
        return std::wstring();
    }

    int length = ::MultiByteToWideChar( codePage, 0, source.data(), source.length(), NULL, 0 );
    if( length == 0 )
    {
        return std::wstring();
    }

    std::vector<wchar_t> buffer( length );
    ::MultiByteToWideChar( codePage, 0, source.data(), source.length(), &buffer[ 0 ], length );

    return std::wstring( buffer.begin(), buffer.end() );
}

template<>
std::string string_cast( const std::wstring& source, unsigned int codePage )
{
    if( source.empty() )
    {
        return std::string();
    }

    int length = ::WideCharToMultiByte( codePage, 0, source.data(), source.length(), NULL, 0, NULL, NULL );
    if( length == 0 )
    {
        return std::string();
    }

    std::vector<char> buffer( length );
    ::WideCharToMultiByte( codePage, 0, source.data(), source.length(), &buffer[ 0 ], length, NULL, NULL );

    return std::string( buffer.begin(), buffer.end() );
}

template<>
std::wstring string_cast( const std::wstring& source, unsigned int codePage )
{
    return source;
}

Существуют ли какие-либо ошибки или дальнейшей оптимизации?



10540
8
задан 9 марта 2011 в 06:03 Источник Поделиться
Комментарии
2 ответа

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

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

Подход не поддается расширяемость. Что произойдет, если вы хотите добавить поддержку другого типа String позже? Совмещение функций вы должны написать взорвется!

Так там же четко возможности для некоторых крупных улучшений. Давайте посмотрим, как мы можем реструктурировать это так, что лучше adheds для сухой. Если вы сделать шаг назад и подумать о том, как string_cast используется, Вы найдете там действительно только 3 сценария он должен поддерживать:


  • бросить в такого же типа строку.

  • приводится к другому типу string.

  • слепок с представлением сырой указатель на тип String.

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

template <typename Td, typename Ts>

string_cast сейчас принимает 2 параметра шаблона. Сохраняя ваш именования, я использую ТС , чтобы указать тип источника. (К и от , наверное, лучшего названия.)

Td string_cast(const Ts &source)
{

Мы используем дедукцию тип определить, что мы проводим кастинг из.

    return string_cast_imp<Td, Ts>::cast(source);

Как только мы знаем, что тип тд и тп - это мы делегируем в string_cast_imp и соответствующий шаблон будет инстанцирован.

}


Давайте ручка удобной случай первый:

template <typename Td>
struct string_cast_imp<Td, Td>
{
static const Td& cast(const Td &source)
{
return source;

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

    }
};


Теперь для важного дела, поводом для написания string_cast в первую очередь! Основной процесс такой же, только в определенных аспектах отличаются:


  • функция преобразования. например. Так и widechartomultibyte против менее multibytetowidechar

  • тип буфера используется. например. вектор строки против вектора для wstring, которая

  • тип строки, возвращаемой. Вот захватили наш шаблон параметр тд , так что мы не придется беспокоиться об этом больше.

Вы можете извлекать эти различия в черта, как класс политики.

template <typename Td, typename Ts>
struct string_cast_imp
{
static Td cast(const Ts &source)
{
int length = string_traits<Ts>::byte_convert( CP_ACP, source.data(), source.length(),
NULL, 0 );
if( length == 0 )
{
return Td();
}

Здесь я удалил строку.пустой() проверить так это на самом деле не нужны. Если строка пуста длина будет 0 в любом случае это должным образом.

        vector< typename string_traits<Td>::char_trait > buffer( length );

Здесь мы используем наши политики с тем, чтобы сообщить нам правильный характер-тип, чтобы использовать для нашего буфера. Если тд = строка тогда string_traits::char_trait будет обугливаться. Если это wstring, которая затем string_traits::char_trait будет преобразовываться в тип wchar_t.

        string_traits<Ts>::byte_convert( CP_ACP, source.data(), source.length(), 
&buffer[ 0 ] , length );

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

        return Td( buffer.begin(), buffer.end() );
}
};

Мы определяем нашу политику string_traits такой:

template <typename T>
struct string_traits;

Объявить общую базу-форме, но не определяют его. Таким образом, если код пытается отлита из типа String illegit это даст ошибку при компиляции.

template <>
struct string_traits<string>
{
typedef char char_trait;
static int byte_convert(const int codepage, LPCSTR data , int data_length,
LPWSTR buffer, int buffer_size)
{

Возможно, вы захотите поиграть с параметрами можно, но это должно дать вам общее представление.

        return ::MultiByteToWideChar( codepage, 0, data, data_length, buffer, buffer_size );
}
};


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

template <typename Td, typename Ts>
Td string_cast(Ts *source)
{
return string_cast_imp<Td, typename string_type_of<const Ts *>::wrap >::cast(source);

Заметьте, что я использую const и Ц * в качестве шаблона параметра для string_type_of. Независимо от того, ТС является константой или нет, мы всегда используем шаблон форма чтобы получить нужную нам информацию.

}

string_type_of другой политики мы определяем следующим образом:

template <typename T>
struct string_type_of;

Этот класс политика говорит нам, что тип String использовать для данного типа необработанного указателя.

template <>
struct string_type_of<const char *>
{
typedef string wrap;
};

С этого рефакторинга, вы сократили количество функций, от 8 до 4 и устранить дублирование кода. Возможно, еще более важно добавление поддержки для другой тип String-это значительно проще, просто специализированных другая политика, что типа String, и вы сделали.

12
ответ дан 26 марта 2011 в 12:03 Источник Поделиться

Поскольку вы перенесли это на codereview, перепроведение моему так ответ:

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

if ( usual_case )
{
// lots of code
}
else
{
// one line handler
}

и вместо этого предпочитают идти с меньшим углублением, при обработке первой ошибки:

if ( ! usual_case )
{
return one_liner;
}
// no need for indentation or braces anymore...

Рерайтинг один из ваших функций будет выглядеть следующим образом:

template<>
std::string string_cast( const wchar_t* pSource, unsigned int codePage )
{
assert( pSource != 0 );
const size_t sourceLength = std::wcslen( pSource );
if( sourceLength == 0 )
{
return std::string();
}

int length = ::WideCharToMultiByte( codePage, 0, pSource, sourceLength, NULL, 0, NULL, NULL );

std::vector<char> buffer( length );
::WideCharToMultiByte( codePage, 0, pSource, sourceLength, &buffer[ 0 ], length, NULL, NULL );

return std::string( buffer.begin(), buffer.end() );
}

Это немного чище: меньше линий, меньше скобок, меньше отступ. Не сильно, лишь слегка, но он добавляет несколько ошибок проверки, и несколько если-отчетность.

Кроме того, я сделал sourceLength константный.

Редактирование объяснений за использование const здесь:


  1. В sourceLength не изменится. Вы собираетесь инициализировать его и использовать его, но вы никогда не собираетесь менять. Используя константный гарантирует, что вы не можете изменить его, и считается хорошей практикой.

  2. Используя если (sourceLength = 0) представляет собой классический программирования ошибка. Проверки на равенство, ==, и присвоение, =, отличаются только одним символом. Это опечатка стала причиной многочисленных ошибок.

    • если sourceLength постоянна, sourceLength = 0 не удается скомпилировать.

    • обращая сравнения и используя если (0 = sourceLength) также ловит ошибку во время компиляции, но это немного странно читать. Кто-то прозвал эту "условия Йоды".


Итак, я сделал это константная соблюдать хороший стиль, и ловить опечатки ошибки-тем более, что я изменил условным с > к ==.

6
ответ дан 9 марта 2011 в 07:03 Источник Поделиться