Skip to content

Commit

Permalink
Publishing version 1.0.0.
Browse files Browse the repository at this point in the history
  • Loading branch information
sawickiap committed Jul 18, 2018
1 parent eb426f3 commit 581da67
Show file tree
Hide file tree
Showing 4 changed files with 368 additions and 50 deletions.
41 changes: 15 additions & 26 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,29 +1,18 @@
BSD 3-Clause License
Copyright 2018 Adam Sawicki

Copyright (c) 2018, Adam Sawicki
All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
117 changes: 109 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,21 @@

Null-termination-aware string-view class for C++.

Author: Adam Sawicki - http://asawicki.info<br>
Version: 1.0.0, 2018-07-18<br>
License: MIT

Documentation: see below and comments in the code of `str_view.hpp` file.

# Introduction

str_view is a small library for C++.
It offers a convenient and optimized class that represents view into a character string.
It has a form of a single header file: `str_view.hpp`, which you can just add to your project.
All the members are defined as inline, so no compilation of additional CPP files or linking with additional libraries is required.
All the members are defined as `inline`, so no compilation of additional CPP files or linking with additional libraries is required.

str_view depends only on standard C and C++ library.
It has been developed and tested under Windows using Microsoft Visual Studio Communiity 2017 Version 15.7.1, but it should work in other compilers and platforms as well. If you find any compatibility issues, please let me know.
It has been developed and tested under Windows using Microsoft Visual Studio Communiity 2017 version 15.7.1, but it should work in other compilers and platforms as well. If you find any compatibility issues, please let me know.
It works in both 32-bit and 64-bit code.

The class is defined as `str_view_template`, because it's a template that can be parametrized with character types. Two typedefs are provided:
Expand Down Expand Up @@ -52,7 +58,7 @@ Foo("Ala ma kota"); // Passed "Ala ma kota"
```cpp
char sz[32];
sprintf(sz, "Number is %i", 7);
sprintf_s(sz, "Number is %i", 7);
Foo(sz); // Passed "Number is 7"
```

Expand Down Expand Up @@ -107,11 +113,13 @@ The class offers powerful `substr` method that returns new view, which may point
str_view orig = "Ala ma kota";
Foo(orig.substr(
4)); // offset
// Passed "ma kota" - substring from offset 0 to the end.
// Passed "ma kota" - substring from offset 4 to the end.
Foo(orig.substr(
0, // offset
3)); // length
// Passed "Ala" - substring limited to 3 characters.
Foo(orig.substr(
4, // offset
2)); // length
Expand All @@ -124,13 +132,20 @@ Foo(orig.substr(

Call `length()` to retrieve length of string view (number of charecters). Alternative name is `size()`, but it's not recommended because its name may be misleading - it may suggest size in bytes not in characters.

`1()` method returns `true` when the string is empty (has length of 0). It may be more efficient than `lenght()`.
`empty()` method returns `true` when the string is empty (has length of 0). It may be more efficient than `length()`.

`data()` method returns a pointer to the underlying character array. Characters are always laid out sequentially in memory, so the pointer may be used as normal C array.

`begin()` and `end()` methods return pointers to the first character and to the character following the last character of the view, respectively. Together they form a range that may be used e.g. with STL algorithms that expect a pair of iterators.
`begin()` and `end()` methods return pointers to the first character and to the character following the last character of the view, respectively. Together they form a range that may be used e.g. with STL algorithms that expect a pair of iterators. They also enable usage of range-based for loop.

Individual characters can be read using overloaded `operator[]`. Alternative syntax is `at()` method. None of them perform range check, for performance reasons.
```cpp
str_view v = str_view("Ala ma kota");
// Prints "Ala ma kota"
for(char ch : v)
printf("%c", ch);
```
Individual characters can be read using overloaded `operator[]`. Alternative syntax is `at()` method. None of them perform range checking, for performance reasons.
First character can also be fetched using `front()` method, and last character is returned by `back()` method.
Expand All @@ -148,10 +163,96 @@ int r = v1.compare(v2,

String view can also be searched and checked using methods: `starts_with()` and `ends_with()` (also supports case-insensitive comparison), `find()`, `rfind()`, `find_first_of()`, `find_last_of()`, `find_first_not_of()`, `find_last_not_of()`.

Last but not least, strings passed in a C++ program often need to end up as null-terminated C strings to be passed to some external libraries, so the class offers `c_str()` method similar to `std::string` that returns pointer to such null-terminated string. It may be either pointer to the original string if it's null terminated, or an internal copy. The copy is valid as long as `str_view` object is alive and it's not modified to point to a different string. It is automatically destroyed.
Last but not least, because strings in a C++ program often need to end up as null-terminated C strings to be passed to some external libraries, the class offers `c_str()` method similar to `std::string` that returns pointer to such null-terminated string. It may be either pointer to the original string if it's null terminated, or an internal copy. The copy is valid as long as `str_view` object is alive and it's not modified to point to a different string. It is automatically destroyed.

```cpp
str_view v = str_view("Ala ma kota");
str_view sub_v = v.substr(4, 2);
printf("sub_v is: %s", sub_v.c_str()); // Prints "sub_v is: ma"
```
# Performance
Unique feature of this library is that a string view is "null-termination-aware" - it not only remembers pointer and length of the referred string, but also the way it was created to avoid unnecessary operations and lazily evaluate those that are requested.
- If it was created from a null-terminated string:
- `c_str()` trivially returns pointer to the original string.
- Length is unknown and it is calculated upon first call to `length()`.
- On the other hand, if it was created from a string that is not null-terminated:
- Length is explicitly known, so `length()` trivially returns it.
- `c_str()` creates a local, null-terminated copy of the string upon first call.
Example 1: View created from a null-terminated string.
```cpp
const char* sz = "Ala ma kota";
str_view v = str_view(sz);
// empty() peeks only first character. Length still unknown.
printf("Empty: %s\n", v.empty() ? "true" : "false"); // Prints "Empty: false"
// length() calculates length on first call.
printf("Length: %zu\n", v.length()); // Prints "Length: 11"
// c_str() trivially returns original pointer.
printf("String is: %s\n", v.c_str()); // Prints "Ala ma kota"
```

Example 2: View created from a STL string.

```cpp
std::string s = "Ala ma kota";
str_view v = str_view(s);

// c_str() returns pointer returned from original s.c_str().
printf("String is: %s\n", v.c_str());
// Length is explicitly known from s, so empty() trivially checks if it's not 0.
printf("Empty: %s\n", v.empty() ? "true" : "false");
// Length is explicitly known from s, so length() trivially returns it.
printf("Length: %zu\n", v.length());
```
Example 3: View created from character array that is not null-terminated.
```cpp
const char* sz = "Ala ma kota";
str_view v = str_view(sz + 4, 2);
// c_str() creates and returns local, null-terminated copy.
printf("String is: %s\n", v.c_str()); // Prints "ma"
// Length is explicitly known, so empty() trivially checks if it's not 0.
printf("Empty: %s\n", v.empty() ? "true" : "false"); // Prints "Empty: false"
// Length is explicitly known, so length() trivially returns it.
printf("Length: %zu\n", v.length()); // Prints "Length: 2"
```

This optimization also works with substrings:

```cpp
str_view vFull = str_view("Ala ma kota");
str_view vBegin = vFull.substr(
0, // offset
3); // length

// Substring is not null-terminated. c_str() creates and returns local, null-terminated copy.
printf("String is: %s\n", vBegin.c_str()); // Prints "Ala"
// Length is explicitly known, so empty() trivially checks if it's not 0.
printf("Empty: %s\n", vBegin.empty() ? "true" : "false"); // Prints "Empty: false"
// Length is explicitly known, so length() trivially returns it.
printf("Length: %zu\n", vBegin.length()); // Prints "Length: 3"
```
```cpp
str_view vFull = str_view("Ala ma kota");
str_view vEnd = vFull.substr(
7); // offset
// Substring is null-terminated. c_str() returns original pointer, adjusted by offset.
printf("String is: %s\n", vEnd.c_str()); // Prints "kota"
// Length is still unknown. empty() peeks only first character.
printf("Empty: %s\n", vEnd.empty() ? "true" : "false"); // Prints "Empty: false"
// length() calculates length on first call.
printf("Length: %zu\n", vEnd.length()); // Prints "Length: 4"
```

# Thread-safety

Despite lazy evaluation, the class is thread-safe. More specifically, `const` methods of a single `str_view` object, including `length()`, `empty()`, and `c_str()`, can be called simultaneously from multiple threads. They are synchronized internally using atomics.
164 changes: 164 additions & 0 deletions Tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,12 @@ static void TestOperators()
str.to_string(returned);
TEST(returned == original);

string returned2;
str.to_string(returned2, 1, 3);
TEST(returned2 == "BCD");
str.to_string(returned2, 3);
TEST(returned2 == "DEF");

// Test operator== and operator!=
str_view str2 = returned;
TEST(str2 == str);
Expand Down Expand Up @@ -442,6 +448,163 @@ static void TestNatvis()
int DEBUG = 1;
}

void Foo1(const str_view& v)
{
printf("String is: %s\n", v.c_str());
}

static void TestDocumentationSamples()
{
// Basic construction

Foo1(str_view()); // Passed ""

Foo1("Ala ma kota"); // Passed "Ala ma kota"

{
char sz[32];
sprintf_s(sz, "Number is %i", 7);
Foo1(sz); // Passed "Number is 7"
}

{
std::string str = "Ala ma kota";
Foo1(str); // Passed "Ala ma kota"
}

// Advanced construction

Foo1(str_view(nullptr)); // Passed ""

{
char array[4] = { 'A', 'B', 'C', 'D' };
Foo1(str_view(array,
4)); // length
// Passed "ABCD"
}

{
const char* sz = "Ala ma kota";
Foo1(str_view(sz + 4,
2)); // length
// Passed "ma"
}

{
std::string str = "Ala ma kota";
Foo1(str_view(str,
4, // offset
2)); // length
// Passed "ma"
}

{
str_view orig = "Ala ma kota";
Foo1(orig.substr(
4)); // offset
// Passed "ma kota" - substring from offset 4 to the end.
Foo1(orig.substr(
0, // offset
3)); // length
// Passed "Ala" - substring limited to 3 characters.
Foo1(orig.substr(
4, // offset
2)); // length
// Passed "ma"
}

// Using string view

{
str_view v1 = str_view("aaa");
str_view v2 = str_view("BBB");
int r = v1.compare(v2,
false); // case_sensitive
// r is -1 because v1 goes before v2 when compared in case-insensitive way.
printf("r = %i\n", r);
}

{
str_view v = str_view("Ala ma kota");
// Prints "Ala ma kota"
for(char ch : v)
printf("%c", ch);
}
printf("\n");

{
str_view v = str_view("Ala ma kota");
str_view sub_v = v.substr(4, 2);
printf("sub_v is: %s\n", sub_v.c_str()); // Prints "sub_v is: ma"
}

// Performance
// Use debugger to confirm described behavior.

{
const char* sz = "Ala ma kota";
str_view v = str_view(sz);

// empty() peeks only first character. Length still unknown.
printf("Empty: %s\n", v.empty() ? "true" : "false"); // Prints "Empty: false"
// length() calculates length on first call.
printf("Length: %zu\n", v.length()); // Prints "Length: 11"
// c_str() trivially returns original pointer.
printf("String is: %s\n", v.c_str()); // Prints "Ala ma kota"
}

{
std::string s = "Ala ma kota";
str_view v = str_view(s);

// c_str() returns pointer returned from original s.c_str().
printf("String is: %s\n", v.c_str());
// Length is explicitly known from s, so empty() trivially checks if it's not 0.
printf("Empty: %s\n", v.empty() ? "true" : "false");
// Length is explicitly known from s, so length() trivially returns it.
printf("Length: %zu\n", v.length());
}

{
const char* sz = "Ala ma kota";
str_view v = str_view(sz + 4, 2);

// c_str() creates and returns local, null-terminated copy.
printf("String is: %s\n", v.c_str()); // Prints "ma"
// Length is explicitly known, so empty() trivially checks if it's not 0.
printf("Empty: %s\n", v.empty() ? "true" : "false"); // Prints "Empty: false"
// Length is explicitly known, so length() trivially returns it.
printf("Length: %zu\n", v.length()); // Prints "Length: 2"
}

{
str_view vFull = str_view("Ala ma kota");
str_view vBegin = vFull.substr(
0, // offset
3); // length

// Substring is not null-terminated. c_str() creates and returns local, null-terminated copy.
printf("String is: %s\n", vBegin.c_str()); // Prints "Ala"
// Length is explicitly known, so empty() trivially checks if it's not 0.
printf("Empty: %s\n", vBegin.empty() ? "true" : "false"); // Prints "Empty: false"
// Length is explicitly known, so length() trivially returns it.
printf("Length: %zu\n", vBegin.length()); // Prints "Length: 3"
}

{
str_view vFull = str_view("Ala ma kota");
str_view vEnd = vFull.substr(
7); // offset

// Substring is null-terminated. c_str() returns original pointer, adjusted by offset.
printf("String is: %s\n", vEnd.c_str()); // Prints "kota"
// Length is still unknown. empty() peeks only first character.
printf("Empty: %s\n", vEnd.empty() ? "true" : "false"); // Prints "Empty: false"
// length() calculates length on first call.
printf("Length: %zu\n", vEnd.length()); // Prints "Length: 4"
}
}

int main()
{
TestBasicConstruction();
Expand All @@ -453,4 +616,5 @@ int main()
TestMultithreading();
TestUnicode();
TestNatvis();
TestDocumentationSamples();
}
Loading

0 comments on commit 581da67

Please sign in to comment.