2013-08-08 14 views
7

więc podczas łączenia ciągów, często nie są stałe elementy, na przykład:Szybsze stały Dołączanie ciąg bez makro

std::string s; 
s += initial_string; 
s += "const string"; 
s += terminating_string; 

To tylko demonstracja, operacje strun może być trochę bardziej skomplikowane i szczegółowe. Tak więc, podczas wykonywania części stałej, implementacja kończy się "niewiedzą" o długości i skutecznie działa na niej strlen(). Oczywiście jest to marnotrawstwo, ponieważ długość jest znana podczas kompilacji. Przetestowałem, że zastąpienie const część ciąg z tym jest trochę szybciej (znacznie bardziej x64 z jakiegokolwiek powodu):

s.append("const string",12); 

To denerwujące, czasochłonne i podatne na błędy faktycznie policzyć znaki , więc jest trochę lepiej:

s.append("const string",sizeof("const string")-1); 

to wciąż nieco podatne na błędy (tj zmienić pierwszą część, ale zapomnij zmienić drugą część), więc makro może pomóc to:

#define strnsizeof(s) s,sizeof(s)-1 
s.append(strnsizeof("const string")); 

Pytanie 1: Czy ktoś ma lepsze/czystsze rozwiązanie tego problemu?

Mam również rozszerzoną klasę ciągów, w której używam operatora << do łączenia ciągów i różnych innych typów obiektów. Podobny problem tutaj, to jest ładne i czyste (dla mnie):

s << initial_string << "const string" << terminating_string; 

Kiedy mam operator dla własnego typu obiektu (którego długość jest składnikiem) operacja append jest szybkie i łatwe, ale kiedy go ponownie dostaje const char *, nie dostaję długości, mimo że jest stała w czasie kompilacji. Więc mogę przyspieszyć że się tworząc mały strukturę, która pobiera const char * i długość wzdłuż linii:

s << initial_string 
    << MyStr::ConstBuf(strnsizeof("const string")) 
    << terminating_string; 

Boy jest to, że coraz brzydkie. Mogę więc również to zrobić, np .:

#define MyStrConst(s) MyStr::ConstBuf(s,sizeof(s)-1) 
s << initial_string 
    << MyStrConst("const string") 
    << terminating_string; 

Lepsze, ale nie świetne.

Pytanie 2: Czy ktoś ma lepsze/czystsze rozwiązanie niż hermetyzacja stałego ciągu?

+3

Funkcja, która pobiera tablicę znaków według odwołania. –

+1

Czy coś jest nie tak z 'std :: stringstream'? – dunc123

+0

Być może brakuje mi czegoś w komentarzu do tablicy znaków ... jak uzyskać długość? na przykład 'void fn (char ar [])' Nie miałbym czasu na kompilację ... – mark

Odpowiedz

2

Napisz do producenta kompilatora i zapytaj go, dlaczego nie optymalizuje dla tego przypadku. Miejmy nadzieję, że dodadzą ciągłą konkatenację ciągów do listy optymalizacji, a kod każdego użytkownika będzie działał szybciej bez konieczności wykonywania żadnych czynności!

To byłoby moje ulubione rozwiązanie.

+0

Uzgodnione. Ale miałem nadzieję, że uda mi się znaleźć jakieś rozwiązanie przed moją emeryturą. =) – mark

+0

Jak uzyskałeś reputację 26,6 tys. Dzięki takim odpowiedziom? (to jest retoryczne, twoja odpowiedź jest jedyna z upvotes ...) –

+0

Rzeczywiście, oczekiwałbym przyzwoitego kompilatora z, powiedzmy, ciągiem libstdC++ najpierw wbudowałby trywialny 'operator + = (p)', który po prostu wywołuje 'append (p) '. Następnie należy zaznaczyć tę banalną funkcję, która (próba debugowania na bok) po prostu wywołuje 'append (p, traits :: length (p))'. Następnie powinien wstawiać 'traits :: length (p)', co jest banalnym opakowaniem wokół 'strlen (p)' (właściwie '__builtin_strlen', ale cokolwiek). A ponieważ 'p' jest znaną stałą po tym wszystkim inline, kompilator powinien zastąpić to znanym wynikiem. Ale najwyraźniej MSVC nie jest na to wystarczająco inteligentny. –

0

co tylko:

const std::string const_string("const string"); 
std::string s; 
s += initial_string; 
s += const_string; 
s += terminating_string; 
+0

Wykonuje względnie dobrze (ale nie tak dobrze jak const/sizeof) i nie chcę wstępnie deklarować wszystkich stałych ciągów. Również 's + = std :: string (" const string ")' idzie w niewłaściwy sposób pod względem wydajności (rodzaj zaskoczenia, że ​​nie można go lepiej zoptymalizować przez kompilator, MSVC2012) ... – mark

0

nie mam dostępu do kompilatora MSVC. Czy zarezerwowanie odpowiednio dużego bufora poprawi wydajność?

Coś wzdłuż tych linii

#include <iostream> 
#include <string> 

using namespace std; 

string fast_concat(string s, const string& terminating_string) { 

    static const string const_string("const string"); 

    s.reserve(s.size() + const_string.size() + terminating_string.size()); 

    s.append(const_string); 

    s.append(terminating_string); 

    return s; 
} 

int main() { 

    cout << fast_concat("initial_string, ", ", terminating string") << endl; 

} 

(mam nadzieję, że do ruchów podczas robienia pierwszego argumentu przez wartość, a także po powrocie wynik.)

+0

Ciąg przed rezerwacją Pamięć masowa faktycznie poprawia wydajność podczas montowania serii łańcuchów o znanej długości, ale jest to inne ulepszenie niż ciągłe łączenie się (szczególnie próbowałem usunąć niepotrzebny "strlen"). – mark

3

Komentarze na pytanie spowodowało w szablonie, podobne następujące:

template<size_t SZ> std::string& operator<<(std::string &s, const char(&arr)[SZ]) { 
    s.append(arr, SZ-1); 
    return s; 
} 

więc zamiast s += "const string" szablon jest stosowany, gdy robi:

s << "const string" 

Dodatkowo udało mi się zaktualizować przedłużony klasy String, tak że następuje wykorzystuje szablon, aby uzyskać stałą wielkość, a także:

s << initial_string << "const string" << terminating_string; 

EDIT: to nie działa zgodnie z oczekiwaniami:

typedef struct { char buffer[32]; } ST; 
ST st = { "1234" }; 
s << st.buffer; // results in s with size 31! 

ten można rozwiązać za pomocą const matrycy, np

template<size_t SZ> std::string& operator<<(std::string &s, char(&arr)[SZ]) { 
    s.append(arr); // NOTE not using SZ here so a strlen happens 
    return s; 
} 

Więc teraz:

s << st.buffer; // results in s with size 4 

wyjątkiem:

const ST cst = &st; 
s << cst.buffer; // results in s with size 31 again... 

sam problem gdy buffer jest w class, jak można się spodziewać.

+1

Upewnij się, że jeśli zwolnisz ten kod, aby inni go używali, dokładnie dokumentuj to zachowanie. Ponieważ przekazywanie zakończonych znakiem null łańcuchów przechowywanych w tablicach znaków, które są dłuższe niż ciąg, nie będzie miało zachowania, którego ludzie oczekują. –

+0

Tak, rzeczywiście, coś, co odkryłem, gdy na początku nie miałem "-1" w dopisie. Również część 'const' parametru' arr' była niezbędna ... – mark

+0

Co więcej, do twoich ustaleń, miejsca mam element członkowski 'char value [Max_Len + 1]', więc 'value' nie wymaga dodatkowej alokacji sterty poza obiektem nadrzędnym teraz wykorzystaj szablon i dołącz cały "Max_Len" bez względu na to, ile pamięci jest używane. Mogę to rozwiązać, tworząc inny szablon, ale bez 'const', a następnie używając operatora' + = ', który powraca do używania 'strlen'. Z wyjątkiem sytuacji, gdy sam obiekt jest const, to wraca do szablonu const ... nie jestem pewien, czy mam rozwiązanie dla tego ... – mark

0

Oto w jaki sposób można uzyskać strlen w czasie kompilacji z szablonem

#include <iostream> 
#include <string> 

using namespace std; 

template <size_t N> 
void concat_char_array(string& s, const char (&array)[N]) { 

    s.append(array, N-1); 
} 

string fast_concat(string s, const string& terminating_string) { 

    concat_char_array(s, "const string"); 

    s.append(terminating_string); 

    return s; 
} 

int main() { 

    cout << fast_concat("initial string, ", ", terminating string") << endl; 

} 

Powinno być tak samo szybko, jak to jest z makr.

+0

Ach, zauważyłem, że inni też to zasugerowali ... – Ali