2015-10-28 11 views
15

Wiem, że wydaje się zbyt dużo Java lub C#. Czy jest jednak możliwe, aby moja własna klasa była ważna jako dane wejściowe dla funkcji std::to_string? Przykład:Tworzenie zdefiniowanej przez użytkownika klasy std :: to_string (stanie)

class my_class{ 
public: 
std::string give_me_a_string_of_you() const{ 
    return "I am " + std::to_string(i); 
} 
int i; 
}; 

void main(){ 
    my_class my_object; 
    std::cout<< std::to_string(my_object); 
} 

Jeśli nie ma czegoś takiego (i myślę, że), co jest najlepszym sposobem, aby to zrobić?

+3

Powiązane [Czy istnieje standardowy sposób konwertowania klasy na ciąg] (http://stackoverflow.com/q/33357480/ 1708801) –

+0

Również http://stackoverflow.com/questions/234724/is-it-possible-to-serialize-and-deserialize-a-class-in-c – Lol4t0

+0

@ShafikYaghmour Tak, może być .. Jednak rozwiązania tam jest próba przeciążenia to_string zamiast tworzenia klasy to_stringable –

Odpowiedz

11

Po pierwsze, niektóre ADL pomagając:

namespace notstd { 
    namespace adl_helper { 
    using std::to_string; 

    template<class T> 
    std::string as_string(T&& t) { 
     return to_string(std::forward<T>(t)); 
    } 
    } 
    template<class T> 
    std::string to_string(T&& t) { 
    return adl_helper::as_string(std::forward<T>(t)); 
    } 
} 

notstd::to_string(blah) zrobi ADL-odnośnika z to_string(blah) z std::to_string zakres.

Następnie zmodyfikować klasę:

class my_class{ 
public: 
    friend std::string to_string(my_class const& self) const{ 
    return "I am " + notstd::to_string(self.i); 
    } 
    int i; 
}; 

i teraz notstd::to_string(my_object) znajdzie właściwą to_string, podobnie jak notstd::to_string(7).

Dzięki odrobinie więcej pracy możemy nawet wspierać metody automatycznego wykrywania i używania typów.

+0

Jak to jest lepsze niż rozwiązanie Richarda? – Slava

+4

@Slava Nie wymaga ręcznego 'używania std :: to_string' przed każdym użyciem' to_string'; nie musisz zanieczyszczać przestrzeni nazw. Zamiast tego, zanieczyszczenie odbywa się tylko w przestrzeni nazw 'notstd :: adl_helper'. Po prostu za każdym razem wywołujesz 'notstd :: to_string'. Używam 'friend to_string', ale jest to odpowiednik darmowej funkcji' to_string'. – Yakk

+2

Richard zatwierdza ten produkt lub usługę. –

1

Prawdopodobnie właśnie chce przeciążać operator<<() coś takiego:

std::ostream& operator << (std::ostream& os, const my_class& rhs) { 
    os << "I am " << rhs.i; 
    return os; 
} 

alternatywnie:

std::ostream& operator << (std::ostream& os, const my_class& rhs) { 
    os << rhs.print_a_string(); 
    return os; 
} 

Następnie można po prostu zrobić:

int main() { 
    my_class my_object; 
    std::cout << my_object; 

    return 0; 
} 
+0

tak, to prawda. ale na przykład nie mogę tego zrobić: 'print_a_string (my_object)' –

+0

Zmieniono odpowiednio odpowiedź, po prostu prześlij zachowanie 'print_a_string' na przeciążenie' operator <<() '(i pozbądź się' print_a_string' tak jak prawdopodobnie nie dłużej potrzebne). Zamiast tego użyj ''print_a_string()' zamiast 'i'. –

1

można zdefiniować własną to_string w własną przestrzeń nazw (np. foo).

namespace foo { 
    std::string to_string(my_class const &obj) { 
    return obj.string give_me_a_string_of_you(); 
    } 
} 

i używać go jako:

int main(){ 
    my_class my_object; 
    std::cout<< foo::to_string(my_object); 
} 

Niestety, nie można zdefiniować własną wersję to_string w przestrzeni nazw std ponieważ acorrding do standardowego 17.6.4.2.1 namespace std [nazw. std](podkreślenie własne):

zachowanie programu C++ jest niezdefiniowany, jeśli dodaje deklaracje lub definicje do przestrzeni nazw std lub do przestrzeni nazw w przestrzeni nazw std , chyba że podano inaczej. Program może dodać szablon specjalizacji dla dowolnego standardowego szablonu biblioteki do obszaru nazw std o tylko , jeśli deklaracja zależy od typu zdefiniowanego przez użytkownika, a specjalizacja spełnia wymagania biblioteki standardowej dla oryginalnego szablonu i nie jest wyraźnie zabroniona.

+0

Dzięki, ale czy nie jest zbyt mylące, aby mieć więcej niż to_string w dwóch różnych przestrzeniach nazw? Nie ma sposobu na użycie tego samego? –

+0

@HumamHelfawi Nie, niestety nie można zdefiniować 'to_string' w' std' dla twojej klasy. – 101010

+0

Myślę, że ostatnia część cytatu (która nie jest pogrubiona) jest ważna. Jeśli 'std :: to_string()' zostało zaimplementowane jako szablon, możesz dokonać specjalizacji 'std :: to_string()' dla twojego typu zdefiniowanego przez użytkownika. Jest on jednak implementowany jako funkcja przeciążona, więc nie można tego zrobić. – Tolli

13

Jaki jest "najlepszy" sposób, to otwarte pytanie.

Istnieje kilka sposobów.

Przede wszystkim należy pamiętać, że przeciążenie std::to_string dla niestandardowego typu to , niedozwolone. Możemy tylko wyspecjalizować funkcje szablonu i klasy w przestrzeni nazw std dla typów niestandardowych, a std::to_string nie jest funkcją szablonu.

To powiedziawszy, dobry sposób leczenia to_string jest bardzo podobny do operatora lub implementacji swap. tj. pozwala na wyszukiwanie zależne od argumentów w celu wykonania pracy.

więc gdy chcemy przekształcić coś na ciąg moglibyśmy napisać:

using std::to_string; 
auto s = to_string(x) + " : " + to_string(i); 

zakładając, że X był obiektem typu X w przestrzeni nazw Y i byłem int, możemy następnie określić:

namespace Y { 

    std::string to_string(const X& x); 

} 

co teraz myśli, że:

powołując to_string(x) rzeczywiście wybiera Y::to_string(const Y::X&) i

powołując to_string(i) wybiera std::to_string(int)

Idąc dalej, to może być to, że chcą to_string zrobić tak samo jak operator < <, więc wtedy można być napisane w kategoriach drugiego:

namespace Y { 

    inline std::ostream& operator<<(std::ostream& os, const X& x) { /* implement here */; return os; } 

    inline std::string to_string(const X& x) { 
    std::ostringstream ss; 
    ss << x; 
    return ss.str(); 
    } 
} 
+0

Pisanie jednego pod względem drugiego ma problemy z wydajnością. – Yakk

+5

pisanie jednego pod względem drugiego * może * mieć problemy z wydajnością, jeśli wykonywane są często w ciasnej pętli. Pozostawia się czytelnikowi decyzję, czy chce on zoptymalizować kosztem utrzymania dwóch ścieżek kodu, czy też nie. –

0

nie możesz dodawać nowych przeciążeniem to_string do std nazw, ale można to zrobić w przestrzeni nazw:

namespace my { 
    using std::to_string; 

    std::string to_string(const my_class& o) { 
    return o.give_me_a_string_of_you(); 
    } 
} 

Następnie można użyć my::to_string dla wszystkich typów.

int main() 
{ 
    my_class my_object; 

    std::cout << my::to_string(my_object); 
    std::cout << my::to_string(5); 
} 
+1

Wadą tego projektu jest to, że musisz zdefiniować wszystkie 'to_string's w' namespace my', a nie w przestrzeni nazw typu. – Yakk

0

Oto alternatywne rozwiązanie. Nic niekoniecznie bardziej eleganckiego lub zbyt wymyślnego, ale jest to podejście alternatywne. Zakłada on, że wszystkie klasy, które zamierzasz wywoływać to_string, mają funkcję ToString().

Oto szablon funkcji, który działa tylko z obiektami typu klasy i wywołuje funkcję ToString().

template<typename T, typename = std::enable_if_t<std::is_class<T>::value>> 
    std::string to_string(const T& t) { 
     return t.ToString(); 
    } 

Być może chcemy, aby działało również z std :: string.

template<> 
    std::string to_string(const std::string& t) { 
     return t; 
    } 

Oto przykład używanego kodu. Zwróć uwagę na fikcyjny obszar nazw to_s. Domyślam się, że jeśli użyjesz std :: to_string w funkcji głównej, to pojawi się ona w naszej nazwie funkcji szablonu, więc musimy wprowadzić nazwę pośrednio w ten sposób. Jeśli ktokolwiek wie, jak to zrobić, byłbym wdzięczny za komentarz.

#include <cstring> 
    #include <iostream> 
    #include <string> 
    #include <type_traits> 


    union U { 
     double d; 
     const char* cp; 
    }; 

    struct A { 
     enum State { kString, kDouble }; 
     State state; 
     U u; 

     void Set(const char* cp) { 
     u.cp = cp; 
     state = kString; 
     } 

     std::string ToString() const { 
     switch (state) { 
      case A::kString : return std::string(u.cp); break; 
      case A::kDouble : return std::to_string(u.d); break; 
      default : return "Invalid State"; 
     } 
     } 
    }; 

    namespace to_s { using std::to_string; }; 

    int main() { 
     using namespace to_s; 
     std::string str = "a nice string"; 
     double d = 1.1; 
     A a { A::kDouble, {1.2} }; 

     std::cout << "str: " << to_string(str) << ", d: " << to_string(d) << std::endl; 
     std::cout << "a: " << to_string(a) << std::endl; 
     a.Set(str.c_str()); 
     std::cout << "a: " << to_string(a) << std::endl; 
     std::memset(&a, 'i', sizeof(a)); 
     std::cout << "a: " << to_string(a) << std::endl; 
    } 

Oto co mam:

str: ładny ciąg d: 1,100000

a: 1,200000

a: ładny ciąg

a: Nieprawidłowy State

Powiązane problemy