Takatoshi Kondo
Takatoshi Kondo

Reputation: 3550

When should I use std::string / std::string_view for parameter / return type

Introduction

I'm writing some communication application. Before C++17 (without Boost), I use std::string and its const reference as cls1.

Since C++17, I introduced std::string_view to my code as cls2. However, I don't have clear policy when should I use std::string_view. My communication application receives data from the network and it is stored to recv_buffer. And creates some application classes from recv_buffer.

Construction

If I focus only cls1's constructor, move construction is efficient. But I think that where the parameter s from. If it is originally from the recv_buffer, I can create std::string_view at the receiving (very early) point. And during recv_buffer's lifetime is enabled, use std::string_view everywhere. If I need to store the part of recv_buffer then create std::string.

An only exception I noticed is the recv_buffer is always contained complete data for my application class. In this case, move construction is efficient.

Getter

I think using the return type as std::string_view has advantage. Some member function such as substr() is efficient. But I don't see any disadvantage, so far.

Question

I suspect that I might see only pros of std::string_view. Before re-writing many codes, I would like to know your ideas.

PoC code

#include <string>

struct cls1 {
    explicit cls1(std::string s):s_(std::move(s)) {}
    std::string const& get() const { return s_; }
private:
    std::string s_;
};

struct cls2 {
    explicit cls2(std::string_view s):s_(s) {}
    std::string_view get() const { return s_; }
private:
    std::string s_;
};

#include <iostream>

int main() {
    // If all of the receive buffer is the target
    {
        std::string recv_buffer = "ABC";
        cls1 c1(std::move(recv_buffer)); // move construct
        std::cout << c1.get().substr(1, 2) << std::endl; // create new string
    }
    {
        std::string recv_buffer = "ABC";
        cls2 c2(recv_buffer);            // copy happend
        std::cout << c2.get().substr(1, 2) << std::endl; // doesn't create new string
    }

    // If a part of the receive buffer is the target
    {
        std::string recv_buffer = "<<<ABC>>>";
        cls1 c1(recv_buffer.substr(3, 3)); // copy happend and move construct
        std::cout << c1.get().substr(1, 2) << std::endl; // create new string
    }
    {
        std::string recv_buffer = "<<<ABC>>>";
        std::string_view ref = recv_buffer;
        cls2 c2(ref.substr(3, 3)); // string create from the part of buffer directly
        std::cout << c2.get().substr(1, 2) << std::endl; // doesn't create new string
    }
}

Running Demo: https://wandbox.org/permlink/TW8w3je3q3D46cjk

Upvotes: 10

Views: 9657

Answers (2)

Michael Chourdakis
Michael Chourdakis

Reputation: 11178

std::string_view is a way to get some std::string const member functions without creating a std::string if you have some char* or you want to reference subset of a string.

Consider it as a const reference. If the object it refers vanishes (or changes) for any reason, you have a problem. If your code can return a reference, you can return a string_view.

Example:

#include <cstdio>
#include <string>
#include <vector>
#include <string.h>
#include <iostream>

int main()
{
    char* a = new char[10];
    strcpy(a,"Hello");
    std::string_view s(a);
    std::cout << s; // OK    
    delete[] a;
    std::cout << s;     // whops. UD. If it was std::string, no problem, it would have been a copy
}

More info.

Edit: It doesn't have a c_str() member because this needs the creation of a \0 at the end of the substring which cannot be done without modification.

Upvotes: 14

eerorika
eerorika

Reputation: 238491

Don't return a string view when:

  • The caller needs a null terminated string. This is often the case when dealing with C API's.
  • You don't store the string itself somewhere. You do store the string in a member in this case.

Do realise, that the string view becomes invalidated by operations on the original string such as changing the capacity, as well as if the original string is destroyed. If the caller needs the string for a longer than the life time of the object that stores the string, then they can copy from the view into their own storage.

Upvotes: 6

Related Questions