First cut of new, non-owning, StringRef class, with no dependencies in header

This commit is contained in:
Phil Nash 2016-06-10 19:37:27 +01:00
parent 3be950958c
commit 2a7d33a38c
5 changed files with 337 additions and 2 deletions

View File

@ -16,6 +16,7 @@
#pragma clang diagnostic ignored "-Wweak-vtables"
#endif
#include "catch_stringref.hpp"
#include "../catch_session.hpp"
#include "catch_registry_hub.hpp"
#include "catch_notimplemented_exception.hpp"

View File

@ -0,0 +1,82 @@
/*
* Copyright 2016 Two Blue Cubes Ltd. All rights reserved.
*
* Distributed under the Boost Software License, Version 1.0. (See accompanying
* file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
*/
#ifndef TWOBLUECUBES_CATCH_STRINGREF_H_INCLUDED
#define TWOBLUECUBES_CATCH_STRINGREF_H_INCLUDED
#include "internal/catch_suppress_warnings.h"
namespace Catch {
// A non-owning string class (similar to the forthcoming std::string_view)
// Note that, because a StringRef may be a substring of another string,
// it may not be null terminated. c_str() must return a null terminated
// string, however, and so the StringRef will internally take ownership
// (taking a copy), if necessary. This internal mutable is not externally
// visible - but does mean StringRefs should not be shared between
// threads.
class StringRef {
protected:
enum class Ownership {
FullStringRef,
SubStringRef,
FullStringOwned
};
using size_type = unsigned long;
char const* m_data;
size_type m_size;
Ownership m_ownership;
static StringRef s_emptyStringRef;
void takeOwnership();
void deleteIfOwned();
// Access for testing purposes only ////////////
friend auto rawCharData( StringRef const& stringRef ) -> char const*;
friend auto isOwned( StringRef const& stringRef ) -> bool;
friend auto isSubstring( StringRef const& stringRef ) -> bool;
////////////////////////////////////////////////
public: // construction/ assignment
StringRef();
StringRef( StringRef const& other );
StringRef( StringRef&& other );
StringRef( char const* rawChars );
StringRef( char const* rawChars, size_type size );
~StringRef();
auto operator = ( StringRef const& other ) -> StringRef&;
auto operator = ( StringRef&& other ) -> StringRef&;
public: // operators
auto operator == ( StringRef const& other ) const -> bool;
auto operator != ( StringRef const& other ) const -> bool;
auto operator[] ( size_type index ) const -> char {
return m_data[index];
}
public: // named queries
auto empty() const -> bool {
return m_size == 0;
}
auto size() const -> size_type {
return m_size;
}
auto c_str() const -> char const*;
public: // substrings and searches
auto substr( size_type start, size_type size ) const -> StringRef;
};
} // namespace Catch
#include "internal/catch_reenable_warnings.h"
#endif // TWOBLUECUBES_CATCH_STRINGREF_H_INCLUDED

View File

@ -0,0 +1,112 @@
#include "catch_stringref.h"
#include <cstring>
namespace Catch {
StringRef StringRef::s_emptyStringRef = "";
StringRef::StringRef()
: StringRef( s_emptyStringRef )
{}
StringRef::StringRef( StringRef const& other )
: m_data( other.m_data ),
m_size( other.m_size ),
m_ownership( other.m_ownership )
{
if( m_ownership == Ownership::FullStringOwned )
m_ownership = Ownership::FullStringRef;
}
StringRef::StringRef( StringRef&& other )
: m_data( other.m_data ),
m_size( other.m_size ),
m_ownership( other.m_ownership )
{
if( m_ownership == Ownership::FullStringOwned )
other.m_ownership = Ownership::FullStringRef;
}
StringRef::StringRef( char const* rawChars )
: m_data( rawChars ),
m_size( std::strlen( rawChars ) ),
m_ownership( Ownership::FullStringRef )
{}
StringRef::StringRef( char const* rawChars, size_type size )
: m_data( rawChars ),
m_size( size ),
m_ownership( Ownership::SubStringRef )
{
size_type rawSize = std::strlen( rawChars );
if( rawSize < size )
size = rawSize;
if( rawSize == size )
m_ownership = Ownership::FullStringRef;
}
StringRef::~StringRef() {
deleteIfOwned();
}
void StringRef::deleteIfOwned() {
if( m_ownership == Ownership::FullStringOwned )
delete m_data;
}
auto StringRef::operator = ( StringRef const& other ) -> StringRef& {
if( &other == this )
return *this;
deleteIfOwned();
m_data = other.m_data;
m_size = other.m_size;
if( other.m_ownership == Ownership::FullStringOwned )
m_ownership = Ownership::FullStringRef;
else
m_ownership = other.m_ownership;
return *this;
}
auto StringRef::operator = ( StringRef&& other ) -> StringRef& {
if( &other == this )
return *this;
deleteIfOwned();
m_data = other.m_data;
m_size = other.m_size;
m_ownership = other.m_ownership;
if( other.m_ownership == Ownership::FullStringOwned )
other.m_ownership = Ownership::FullStringRef;
return *this;
}
auto StringRef::c_str() const -> char const* {
if( m_ownership == Ownership::SubStringRef )
const_cast<StringRef*>( this )->takeOwnership();
return m_data;
}
void StringRef::takeOwnership() {
if( m_ownership != Ownership::FullStringOwned ) {
char* data = new char[m_size+1];
std::memcpy( data, m_data, m_size );
data[m_size] = '\0';
m_data = data;
m_ownership = Ownership::FullStringOwned;
}
}
auto StringRef::substr( size_type start, size_type size ) const -> StringRef {
if( start < m_size )
return StringRef( m_data+start, size );
else
return StringRef();
}
auto StringRef::operator == ( StringRef const& other ) const -> bool {
return size() == other.size() &&
(std::strncmp( m_data, other.m_data, size() ) == 0);
}
auto StringRef::operator != ( StringRef const& other ) const -> bool {
return !operator==( other );
}
} // namespace Catch

View File

@ -0,0 +1,131 @@
#include "internal/catch_stringref.h"
#include "internal/catch_suppress_warnings.h"
// !TBD always owning string implementation
namespace Catch {
class String {
public:
String() {}
};
}
namespace Catch {
}
#include <string>
namespace Catch {
// Implementation of test accessors
auto rawCharData( StringRef const& stringRef ) -> char const* {
return stringRef.m_data;
}
auto isOwned( StringRef const& stringRef ) -> bool {
return stringRef.m_ownership == StringRef::Ownership::FullStringOwned;
}
auto isSubstring( StringRef const& stringRef ) -> bool {
return stringRef.m_ownership == StringRef::Ownership::SubStringRef;
}
// Allow StringRefs to be converted to std::strings for printing
// - this will become redundant when toString is reimplemented in terms
// of String/StringRef
inline auto toString( StringRef const& stringRef ) -> std::string {
return std::string( rawCharData( stringRef ), stringRef.size() );
}
}
#include "catch.hpp"
TEST_CASE( "StringRef" ) {
using Catch::StringRef;
SECTION( "Empty string" ) {
StringRef empty;
REQUIRE( empty.empty() );
REQUIRE( empty.size() == 0 );
REQUIRE( std::strcmp( empty.c_str(), "" ) == 0 );
}
SECTION( "From string literal" ) {
StringRef s = "hello";
REQUIRE( s.empty() == false );
REQUIRE( s.size() == 5 );
REQUIRE( isSubstring( s ) == false );
auto rawChars = rawCharData( s );
REQUIRE( std::strcmp( rawChars, "hello" ) == 0 );
SECTION( "c_str() does not cause copy" ) {
REQUIRE( isOwned( s ) == false );
REQUIRE( s.c_str() == rawChars );
REQUIRE( isOwned( s ) == false );
}
}
SECTION( "From sub-string" ) {
StringRef original = StringRef( "original string" ).substr(0, 8);
REQUIRE( original == "original" );
REQUIRE( isSubstring( original ) );
REQUIRE( isOwned( original ) == false );
original.c_str(); // Forces it to take ownership
REQUIRE( isSubstring( original ) == false );
REQUIRE( isOwned( original ) );
}
SECTION( "Substrings" ) {
StringRef s = "hello world!";
StringRef ss = s.substr(0, 5);
SECTION( "zero-based substring" ) {
REQUIRE( ss.empty() == false );
REQUIRE( ss.size() == 5 );
REQUIRE( std::strcmp( ss.c_str(), "hello" ) == 0 );
REQUIRE( ss == "hello" );
}
SECTION( "c_str() causes copy" ) {
REQUIRE( isSubstring( ss ) );
REQUIRE( isOwned( ss ) == false );
auto rawChars = rawCharData( ss );
REQUIRE( rawChars == rawCharData( s ) ); // same pointer value
REQUIRE( ss.c_str() != rawChars );
REQUIRE( isSubstring( ss ) == false );
REQUIRE( isOwned( ss ) );
REQUIRE( rawCharData( ss ) != rawCharData( s ) ); // different pointer value
}
SECTION( "non-zero-based substring") {
ss = s.substr( 6, 6 );
REQUIRE( ss.size() == 6 );
REQUIRE( std::strcmp( ss.c_str(), "world!" ) == 0 );
}
SECTION( "Pointer values of full refs should match" ) {
StringRef s2 = s;
REQUIRE( s.c_str() == s2.c_str() );
}
SECTION( "Pointer values of substring refs should not match" ) {
REQUIRE( s.c_str() != ss.c_str() );
}
}
SECTION( "Comparisons" ) {
REQUIRE( StringRef("hello") == StringRef("hello") );
REQUIRE( StringRef("hello") != StringRef("cello") );
}
}

View File

@ -20,6 +20,7 @@
2691574C1A532A280054F1ED /* ToStringTuple.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2691574B1A532A280054F1ED /* ToStringTuple.cpp */; };
2694A1FD16A0000E004816E3 /* catch_text.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2694A1FB16A0000E004816E3 /* catch_text.cpp */; };
269E42321C04E21300133E05 /* ThreadedTests.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 269E42311C04E21300133E05 /* ThreadedTests.cpp */; };
26ADDC0A1D077DB3008F7108 /* StringTests.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26ADDC091D077DB3008F7108 /* StringTests.cpp */; };
26E1B7D319213BC900812682 /* CmdLineTests.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26E1B7D119213BC900812682 /* CmdLineTests.cpp */; };
4A45DA2416161EF9004F8D6B /* catch_console_colour.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4A45DA2316161EF9004F8D6B /* catch_console_colour.cpp */; };
4A45DA2716161F1F004F8D6B /* catch_ptr.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4A45DA2616161F1F004F8D6B /* catch_ptr.cpp */; };
@ -106,6 +107,9 @@
269831E719121CA500BB0CE0 /* catch_reporter_compact.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = catch_reporter_compact.hpp; sourceTree = "<group>"; };
269E42311C04E21300133E05 /* ThreadedTests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ThreadedTests.cpp; path = ../../../SelfTest/ThreadedTests.cpp; sourceTree = "<group>"; };
269E42331C04E33D00133E05 /* catch_thread_context.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = catch_thread_context.hpp; sourceTree = "<group>"; };
26ADDC091D077DB3008F7108 /* StringTests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StringTests.cpp; path = ../../../SelfTest/StringTests.cpp; sourceTree = "<group>"; };
26ADDC0B1D0B394D008F7108 /* catch_stringref.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = catch_stringref.h; sourceTree = "<group>"; };
26ADDC0C1D0B3969008F7108 /* catch_stringref.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = catch_stringref.hpp; sourceTree = "<group>"; };
26AEAF1617BEA18E009E32C9 /* catch_platform.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = catch_platform.h; sourceTree = "<group>"; };
26DACF2F17206D3400A21326 /* catch_text.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = catch_text.h; sourceTree = "<group>"; };
26DFD3B11B53F84700FD6F16 /* catch_wildcard_pattern.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = catch_wildcard_pattern.hpp; sourceTree = "<group>"; };
@ -208,6 +212,7 @@
26059AF11BD4B94C003D575C /* PartTrackerTests.cpp */,
26E1B7D119213BC900812682 /* CmdLineTests.cpp */,
26711C8D195D465C0033EDA2 /* TagAliasTests.cpp */,
26ADDC091D077DB3008F7108 /* StringTests.cpp */,
);
name = "Introspective Tests";
sourceTree = "<group>";
@ -361,6 +366,7 @@
2627F7061935B55F009BCE2D /* catch_result_builder.hpp */,
26711C92195D48F60033EDA2 /* catch_tag_alias_registry.hpp */,
269E42331C04E33D00133E05 /* catch_thread_context.hpp */,
26ADDC0C1D0B3969008F7108 /* catch_stringref.hpp */,
);
name = impl;
sourceTree = "<group>";
@ -460,6 +466,7 @@
2656C227192A78410040DB02 /* catch_reenable_warnings.h */,
263F7A4519A66608009474C2 /* catch_fatal_condition.hpp */,
26DFD3B11B53F84700FD6F16 /* catch_wildcard_pattern.hpp */,
26ADDC0B1D0B394D008F7108 /* catch_stringref.h */,
);
name = Infrastructure;
sourceTree = "<group>";
@ -541,6 +548,7 @@
4AEE032316142FC70071E950 /* catch_debugger.cpp in Sources */,
269E42321C04E21300133E05 /* ThreadedTests.cpp in Sources */,
4AEE032516142FF10071E950 /* catch_stream.cpp in Sources */,
26ADDC0A1D077DB3008F7108 /* StringTests.cpp in Sources */,
4AEE0328161434FD0071E950 /* catch_xmlwriter.cpp in Sources */,
4A45DA2416161EF9004F8D6B /* catch_console_colour.cpp in Sources */,
4A45DA2716161F1F004F8D6B /* catch_ptr.cpp in Sources */,
@ -548,6 +556,7 @@
2656C2211925E7330040DB02 /* catch_test_spec.cpp in Sources */,
4A45DA2916161F3D004F8D6B /* catch_streambuf.cpp in Sources */,
4A45DA2B16161F79004F8D6B /* catch_interfaces_registry_hub.cpp in Sources */,
26ADDC0D1D0B3969008F7108 /* catch_stringref.hpp in Sources */,
4A45DA2D16161FA2004F8D6B /* catch_interfaces_capture.cpp in Sources */,
4A45DA3116161FFC004F8D6B /* catch_interfaces_reporter.cpp in Sources */,
4A45DA3316162047004F8D6B /* catch_interfaces_exception.cpp in Sources */,
@ -572,7 +581,7 @@
buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "c++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_WARN_CXX0X_EXTENSIONS = YES;
CLANG_WARN_CXX0X_EXTENSIONS = NO;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
@ -625,7 +634,7 @@
buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "c++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_WARN_CXX0X_EXTENSIONS = YES;
CLANG_WARN_CXX0X_EXTENSIONS = NO;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;