LINUX.ORG.RU

Перегрузка функций, принимающих строки в C++

 ,


0

4

В стандартной библиотеке C++ есть куча функций вида:

void do(const char *text);
void do(const std::string &text);

Да, вроде здорово: можно передать и «сырую» строку и объект. А вот как быть, если строковых параметров два:

void set(const char *key, const char *val);

inline void set(const std::string &key, const char *val) {
    set(key.c_str(), val);
}

inline void set(const char *key, const std::string &val) {
    set(key, val.c_str());
}

inline void set(const std::string &key, const std::string &val) {
    set(key.c_str(), val.c_str());
}

Уже многовато перегрузок как-то. Пока что вообще не пишу перегрузок, принимающих std::string, но приписывать постоянно .c_str() некрасиво.

А можно написать так:

void set(std::string key, std::string val);

С точки зрения использования сплошная красота: принимаются оба типа строк, всего одна функция. Но если передать такой функции «сырую» строку, то она будет лишний раз скопирована в объект (поскольку от COW-реализации строк в C++ отказались), что нехорошо.

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

И ещё вопрос по оптимизациям в компиляторе. Вот такой код:

inline void set(std::string key, std::string val) {
    set_cargs(key.c_str(), val.c_str());
}

Может быть соптимизирован компилятором так, что создание промежуточного string не будет производиться, если оба параметра — const char *? Насколько я понимаю, так не получится, но может всё же кто-то сталкивался?

★★

Последнее исправление: beastie (всего исправлений: 2)

Не, ты реши - тебе оптимизация (ты уверен, что она тебе именно тут нужна?) или обобщенность.

Pavval ★★★★★
()
Ответ на: комментарий от Pavval

В практическом смысле я решил: использую только const char*. Но это решение, основанное на моих знаниях. А может есть техника, позволяющая и обобщить и в то же время получать оптимизированный вариант.

uuwaan ★★
() автор топика

Самый универсальный метод:

void set(const std::string &key, const std::string &val) {
    set_cargs(key.c_str(), val.c_str());
}
А мешать std::string и char*, имхо, моветон.

NegatiV
()
Ответ на: комментарий от uuwaan

Только если навелосипедишь свой класс строк который будет уметь CoW.

NegatiV
()
void do(const std::string &text);

Данной функции можно передать «сырую» строку.

void do("test");
Так что это самый лучше вариант. Когда передается «сырая» строка, то в функции создается объект string. Когда передается string, то в функции находится ссылка на этот объект.

anatoly
()

Если я правильно понимаю, то существует обычное применяемое решение. Реализуем самую общую функцию, а через нее реализуем дополнительные вспомогательные функции функции:

void set(const char *key, const char *val)
{
   //implemetation, using key and val
   // ...
}
void set(const string &key, const char *val)
{
    set(key.c_str(), val);
}
void set(const char *key, const string &val)
{
    set(key, val.c_str());
}
void set(const string &key, const string &val)
{
    set(key.c_str(), val.c_str());
}
//// void set(string key, string val) ОБЫЧНО НЕ ТРЕБУЕТСЯ

Все просто и понятно.

Deleted
()
Последнее исправление: Deleted (всего исправлений: 1)
Ответ на: комментарий от anatoly

Да, в случае, если основная (самая общая) реализация алгоритма идет на string, а не не char *, твой вариант предпочтительней.

Deleted
()
Последнее исправление: Deleted (всего исправлений: 1)
Ответ на: комментарий от Deleted

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

uuwaan ★★
() автор топика
Ответ на: комментарий от Deleted

А если аргумента не два, а три? Имхо, такой способ необходим только если нужно плюсовое приложение/либу скрестить с сишной либой, тогда, да, пишем обертку:

public:
    void set(const std::string &key, const std::string &value) {
        this->set(key.c_str(), value.c_str());
    }

private:
    void set(const char *key, const char *value) {
        some_c_lib_func(key, value);
    }

NegatiV
()

А можно написать так:

void set(std::string key, std::string val);

С точки зрения использования сплошная красота: принимаются оба типа строк, всего одна функция.

Красота, да не совсем. Насколько я знаю, инициализировать std::string нулевым указателем нельзя, а некоторые функции libc, например, возвращают либо const char*, либо NULL. Так что этот момент еще нужно отдельно учитывать.

m0rph ★★★★★
()
Ответ на: комментарий от uuwaan
int length(const std::string & s) { return s.size(); }
int length(const char * s) { return strlen(s); }
E ★★★
()

Так стандартная библиотека крестиков наполовину состоит из наследия сишки. В новый-то код зачем тащить голые стоки? Уберхайлоад с парсингом гигабайтных строк пишешь небось?

vazgen05 ★★★
()

Сделай отдельный тип с конструкторами, принимающими и char* и string.

crowbar
()
Ответ на: комментарий от NegatiV

А если аргумента не два, а три?

#include <string>

void set(const char *s1, const char *s2, const char *s3)
{
    /// Common implementation
    (void*)s1;(void*)s2;(void*)s3;
}
const char *conv(const char *s)
{
    return s;
}
const char *conv(const std::string &s)
{
    return s.c_str();
}

template<typename T1, typename T2, typename T3>
void set(const T1 &s1, const T2 &s2, const T3 &s3)
{
    set(conv(s1), conv(s2), conv(s3));
}

int main()
{
    std::string s1;
    std::string s2;
    const char * s3;

    set(s1, s2, s3);
    set("Hello!", s3, s1);
    return 0;
}

:) http://ideone.com/52dAYl

Имхо, такой способ необходим только если нужно плюсовое приложение/либу скрестить с сишной либой

Ну, а для чего еще использовать const char * ... А вообще, у любой проблемы есть два решения: на уровне костылей в коде и перепроектирования.

Deleted
()
Ответ на: комментарий от m0rph

Я имел в виду не столько забирание из libc, сколько вещи вида:

set("settings.home_page", url);

Где, url — std::string, а первый параметр — строковая константа, которую в std::string загонять каждый раз перед вызовом не айс.

uuwaan ★★
() автор топика
Ответ на: комментарий от uuwaan
namespace {
    const std::string &SETTINGS_HOMEPAGE = "settings.home_page";
}

За литеральные константы в коде бить по рукам надо.

NegatiV
()
Последнее исправление: NegatiV (всего исправлений: 1)
Ответ на: комментарий от uuwaan

set(«settings.home_page», url);

если тебе действительно требуется вот это вот - то используй std::string и не парься, это не интероп между Си <=> Си++. А лучше делай свой тип для key - Key с разными конструкторами, как сказали выше.

struct Key {
Key(const char *a);
Key(const std::string &a);
// ...
private:
char key_[100500];
};
Deleted
()
Последнее исправление: Deleted (всего исправлений: 1)
Ответ на: комментарий от NegatiV

вот C++-функция, а вот враппер для передачи аргументов в C-либу.

в моем примере немного наоборот: вот C-функция ( set(char ...) ), а вот C++-обертка для нее ( conv()+template set() ) - никакого уродство. Но ТС получается нужно не это, я считаю нужен class Key для ключа.

Deleted
()

что нехорошо.

Ну да, а 2^n перегрузок на каждую функцию это ок, программист распарсит. Он что, не программист штоле?!

comp00 ★★★★
()
Ответ на: комментарий от Deleted

Нет, мне как раз надо именно то, что ты написал, спасибо.

uuwaan ★★
() автор топика

Можно использовать string shims из STLSoft или написать такие же:

template<typename T1, typename T2>
void foo(const T1 & s1, const T2 & t2)
{
    printf("s1 = %s, s2 = %s\n", c_str_ptr(s1), c_str_ptr(s2));
}

foo(std::string("bar"), "baz");
foo("bar", std::string("baz"));

IIRC, идиому shims (прокладки) придумал автор библиотеки.

PS. Его же книга.

gv
()
Ответ на: комментарий от gv

Ого. Спасибо, очень интересная идиома. После беглого пролистывания кажется, её специально и придумали, чтобы нужные перегрузки лепить через template.

uuwaan ★★
() автор топика

Если компилятор поддерживает С++11, и если параметров три, четыре и т.д, то рекурсивные темплейты спасут мир (только на этапе компиляции, естессна).

Как-то так, например:

#include <iostream>
#include <tuple>
#include <typeinfo>

using namespace std;

// example set functions
void set(const char *x1, const char *x2, const char *x3) {
    cout << "do something with " << x1 << ", " << x2 << ", " << x3 << endl;
}

void set(const char *x1, const char *x2) {
    cout << "do something with " << x1 << ", " << x2 << endl;
}

// final call of set
template < typename ... >
struct processor {};

template < typename ...T2 >
struct processor < tuple <>, tuple< T2... > >  {
  void operator()(T2... t2) {
    set(t2...);
  }
};

// common case (default)
template < typename X,
           typename ...T1,
           typename ...T2 >
struct processor <tuple< X, T1... >, tuple< T2...> >  {
  void operator()(X x, T1... t1, T2... t2) {
    auto item = processor < tuple < T1... >, tuple < T2..., const char *> >();
    item(t1..., t2..., (const char*)x);
  }
};

// partial overload
template < typename ...T1,
           typename ...T2 >
struct processor <tuple< string, T1... >, tuple< T2...> >  {
  void operator()(string x, T1... t1, T2... t2) {
    auto item = processor < tuple < T1... >, tuple < T2..., const char *> >();
    item(t1..., t2..., x.c_str());
  }
};

template < typename ...T1,
           typename ...T2 >
struct processor <tuple< const char *, T1... >, tuple< T2...> >  {
  void operator()(const char *x, T1... t1, T2... t2) {
    auto item = processor < tuple < T1... >, tuple < T2..., const char *> >();
    item(t1..., t2..., x);
  }
};

template < typename ...T1,
           typename ...T2 >
struct processor <tuple< int, T1... >, tuple< T2...> >  {
  void operator()(int x, T1... t1, T2... t2) {
    auto item = processor < tuple < T1... >, tuple < T2..., const char *> >();
    char buffer[10];
    snprintf(buffer,10,"%d",x);
    item(t1..., t2..., buffer);
  }
};


template < typename ...Ts >
void set(Ts ...ts) {
  auto item = processor < tuple < Ts... >, tuple <> >();
  item(ts...);
}

int main () {
  set("1",string("2"), 3);
  set("5", 6);
}

В принципе, можно передавать любые типы. Можно частично перегружать темплейт (как в этом примере) сделано, а можно перегрузить приведение разных типов к типу const char* (и юзать только дефолтный темплейт).

Можно выделить первый элемент в отдельный тип или даже передавать в него указатель на функцию (set). Вариантов море.

Iron_Bug ★★★★★
()
Ответ на: комментарий от uuwaan

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

Ага, так и есть. Советую посмотреть книжку по ссылке, автор придумал кучу разных полезных приемов и довольно интересно про них написал.

gv
()
Ответ на: комментарий от Iron_Bug

Писал что-то подобное когда свой RPC-сервер на протобуфере велосипедил, получилось что-то вроде:

std::tie(from, path, linkName) = RPC::Proto::get_args<uint64_t, std::string, std::string>(call.args());
Но реализацию всего этого добра надо прятать куда подальше - мало читаемо из-за обилия ... < > и прочего.

NegatiV
()
Ответ на: комментарий от NegatiV

Но реализацию всего этого добра надо прятать куда подальше - мало читаемо из-за обилия ... < > и прочего.

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

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

ну и с С++11 я вынуждена аккуратно обращаться: не все компиляторы на всех основных системах его поддерживают (не будем показывать пальцем, хотя это мелкомягкие). поэтому иногда ради кроссплатформы приходится отказываться от плюшек или городить противные ifdef'ы.

Iron_Bug ★★★★★
()
Ответ на: комментарий от Iron_Bug

По-моему можно проще сделать:

#include <stdio.h>
#include <string>

void set(const char *str1, const char *str2, const char *str3) {
  printf("set: %s %s %s\n", str1, str2, str3);
}

void set1(const char *str1, const char *str2, int i) {
  printf("set1: %s %s %d\n", str1, str2, i);
}

template <typename F, typename T>
T switch_cast(const F &f) {
    return static_cast<T>(f);
}

template<>
const char *switch_cast<std::string>(const std::string &str) {
  return str.c_str();  
}

template <typename R, typename ... CArgs, typename ... Args>
void invoke(R (*ptr)(CArgs ...), Args ... args) {
    ptr(switch_cast<Args, CArgs>(args) ...);
}

int main(int , char **) {
  std::string arg = "456";
  
  invoke(set, "123", arg, "789");
  invoke(set, "123", std::string("123"), "123");
  invoke(set1, "123", "123", 2);
  
  return 0;  
}

NegatiV
()
Ответ на: комментарий от NegatiV

да, красивое решение! :) вот чем мне нравится С++ - так это тем, что одну задачу можно решить как минимум десятью разными способами. программирование из ремесла превращается в искусство.

Iron_Bug ★★★★★
()
Ответ на: комментарий от intelfx

Это просто пример того что так можно транслировать не только аргументы типа std::string в const char *. Сделав, например, уточнение switch_cast:

template<>
const char *switch_cast<int>(int i) {
  // тут магия, допустим со sstream и дальнейшим вызовом return switch_cast<std::string, const char *>( ... )
} 
Можно транслировать int'ы в const char * там где это нужно и, к сожалению, не нужно.

NegatiV
()
Ответ на: комментарий от intelfx

А, просто на коленке писал код. =) Лень было другое имя придумывать, вообще можно тоже set назвать.

UPD. возможно будет неоднозначность cons char * <=> int

NegatiV
()
Последнее исправление: NegatiV (всего исправлений: 1)
void do(const char *text);
void do(const std::string &text);

не взлетит.

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.