C++ std::string 메모리 관리 포함 거의 모든 멤버함수까지 직접 구현하기
Short Coding

C++ std::string 메모리 관리 포함 거의 모든 멤버함수까지 직접 구현하기

Woon2World :: Programming & Art Life

 

 

 

std 라이브러리의 클래스들을 직접 구현해보면
언어 기능에 대해 눈이 좀 더 뜨일 것이다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main()
{
    using string = my_string<char>;
 
    string str[3= {"Hello, ""World!\n""C++!\n"};
 
    std::cout << str[0+ str[1];
    std::cout << str[0].append(str[2]);
    std::cout << str[2].insert(3string(" Programming Language"));
    std::cout << str[2].erase(12);
    std::cout << str[2].replace(01"Java");
 
    assert(str[2== string{"Java Programming Language!\n"});
    assert(str[1== string{str[1]});
}
cs

 

 

구현하지 못한 내용

  • std::basic_string는 character traits와 allocator까지 template argument로 받는데,
    이 둘은 구현하지 않았다.
    character traits와 allocator에서 제공하는 함수들은 별도의 전역 함수나 static 멤버 함수로 대체했다.
  • std::basic_string은 SSO(Short String Optimization)을 제공하지만, 구현하지 않았다.
  • std::string_view와 호환되는 함수들, find를 제외한 find계열 함수들(find_first_of, find_last_pf 등)은 구현하지 않았다.

메모

계층적 호출 vs 직접적 호출

insert와 push_back, append, erase와 같은 함수들을 replace를 이용해 구현하고,

replace에선 디버그용 선행 조건 검사만 한 뒤 private 함수인 __mutate를 호출하도록 했다.

 

insert는 바로 __mutate를 호출하는 것이 좋을까, replace를 호출하는 것이 좋을까

append는 insert를 호출하는 것이 좋을까, replace를 호출하는 것이 좋을까

replace의 오버로드된 버전들은 바로 __mutate를 호출하는 것이 좋을까, 다른 오버로딩을 호출하는 것이 좋을까

이러한 고민을 많이했다.

 

이용할 수 있는 함수가 많은 상황에서,

계층적인 호출 구조를 만드냐 직접적인 호출 구조를 만드냐를 선택해야 했다.

 

계층적인 호출 구조를 만들면,

구현을 수정해야 할 때 읽어야 할 코드가 계층 전체다.

대신에 구현을 훨씬 캡슐화할 수 있다.

 

직접적인 호출 구조를 만들면,

구현을 수정해야 할 때 모든 내부 동작을 일관하는 private 함수 하나만 수정하면 된다.

그러나 그 private 함수의 영향력이 매우 큰 나머지 수정이 두려워질 수 있다.

 

경험상 거의 모든 함수와 클래스에 이용될 수 있는 마스터키를 갖추는 것은 좋은 선택이 아니었다.

전역 변수가 비슷한 성격을 가지고 있으나 지양되는 것을 고려하면,

계층적인 호출 구조를 선택하는 게 좋은 선택일 것이라 판단했다.


swap 활용

멤버 함수 swap을 구현하여 copy & swap idiom을 활용하면

많은 메모리 조작 함수들의 줄 수를 획기적으로 줄일 수 있었다.

 

r-value는 swap의 인자로 전달하지 못하므로,

this->swap(T(args...)) 형태 대신

T(args...).swap(*this) 형태로 코딩한다.

원하는대로 동작하고 컴파일 오류가 나지 않을 것이다.


this->

템플릿 클래스의 멤버에 접근할 때 this->를 표기하지 않으면 컴파일 오류가 나는 상황을 많이 보았다.

그래서 이번에 코딩할 때에도 this->를 막무가내로 적었는데,

this->가 필요했던 상황을 알게 되었다.

 

전역 함수와 이름이 겹치는 경우 (이름 탐색 규칙이 있어 오류는 안 날 테지만)

어떤 것을 호출하는지 명확히 하기 위해 this->를 적거나,

 

부모 템플릿 클래스의 멤버에 접근하기 위해서 this->를 적는 것이다.

(부모 템플릿 클래스의 어떤 특수화는 해당 멤버를 갖고 있지 않을 수 있기 때문에)


Coding Style

gcc의 코딩 스타일을 보고 모방하느라

attribute & constexpr, return type qualifier, return type, function name & function qualifier가

각기 별개의 줄이 되었다.

 

일관성있게 작성하니 가독성이 나쁘지 않아 보이고 소스의 가로 길이 제한을 지키는데 효과적이긴 하다.

하지만, 코드의 줄 수가 상당히 늘어나고 함수의 몸체보다 머리가 더 커지는 현상이 발생한다.

이러한 대두 함수들이 정말로 가독성을 효과적으로 늘리는지는 의문이다.

 

이런 스타일을 적용해본 것이 처음이기 때문에,

조금 더 적응해보고 나서 장단을 파악해야 할 것 같다.

코드

  • my_string.h
#ifndef _my_string
#define _my_string
 
#include "my_exception.h"
#include <algorithm>
#include <cctype>
#include <iostream>
 
#define _check_i_is_in_size(i, strobj, emsg) \
    debug_check_out_of_range((i), std::size_t{ 0 }, \
                            (strobj).size(), (emsg))
 
template <class CharT>
class my_string
{
public:
    static constexpr std::size_t npos = -1;
 
    // constructors *************************************
    
    constexpr
    my_string() noexcept;
    
    my_string(std::size_t count, CharT ch);
    
    my_string(const my_string& other, std::size_t pos,
              std::size_t count = npos);
              
    my_string(const CharT* str, std::size_t count);
    
    my_string(const CharT* str);
    
    my_string(const my_string& other);
    
    my_string(my_string&& other) noexcept;
    
    my_string(std::nullptr_t) = delete;
    
    // ************************************* constructors
    
    // destructor ***************************************
    
    ~my_string();
    
    // *************************************** destructor
    
    // assignment operators *****************************
    
    my_string&
    operator=(const my_string& other);
    
    my_string&
    operator=(my_string&& other) noexcept;
    
    my_string&
    operator=(const CharT* str);
    
    my_string&
    operator=(CharT ch);
    
    // ***************************** assignment operators
    
    // assign *******************************************
    
    my_string&
    assign(std::size_t count, CharT ch);
    
    my_string&
    assign(const my_string& str,
           std::size_t pos = std::size_t{ 0 },
           std::size_t count = npos);
                
    my_string&
    assign(my_string&& str) noexcept;
    
    my_string&
    assign(const CharT* str, std::size_t count);
    
    my_string&
    assign(const CharT* str);
    
    // ******************************************* assign
    
    // element access ***********************************
    
    [[nodiscard]] constexpr
    CharT&
    at(std::size_t pos);
    
    [[nodiscard]] constexpr
    const CharT&
    at(std::size_t pos) const;
        
    [[nodiscard]] constexpr
    CharT&
    operator[](std::size_t index);
        
    [[nodiscard]] constexpr
    const CharT&
    operator[](std::size_t index) const;
        
    [[nodiscard]] constexpr
    CharT&
    front();
    
    [[nodiscard]] constexpr
    const CharT&
    front() const;
    
    [[nodiscard]] constexpr
    CharT&
    back();
    
    [[nodiscard]] constexpr
    const CharT&
    back() const;
    
    [[nodiscard]] constexpr
    CharT*
    data() noexcept;
    
    [[nodiscard]] constexpr
    const CharT*
    data() const noexcept;
    
    [[nodiscard]] constexpr
    const CharT*
    c_str() const noexcept;
    
    // *********************************** element access
    
    // capacity *****************************************
    
    [[nodiscard]] constexpr
    bool
    empty() const noexcept;
    
    [[nodiscard]] constexpr
    std::size_t
    size() const noexcept;
    
    [[nodiscard]] constexpr
    std::size_t
    length() const noexcept;
    
    [[nodiscard]] constexpr
    std::size_t
    capacity() const noexcept;
    
    void
    reserve(std::size_t new_cap);
 
    void
    shrink_to_fit();
    
    // ***************************************** capacity
    
    // operations ***************************************
    
    void
    clear() noexcept;
    
    my_string&
    insert(std::size_t index, std::size_t count,
           CharT ch);
    
    my_string&
    insert(std::size_t index, const CharT* str);
    
    my_string&
    insert(std::size_t index, const CharT* str,
           std::size_t count);
        
    my_string&
    insert(std::size_t index, const my_string& str);
    
    my_string&
    insert(std::size_t index, const my_string& str,
           std::size_t index_str, std::size_t count = npos);
        
    my_string&
    replace(std::size_t pos, std::size_t count,
            const my_string& str);
    
    my_string&
    replace(std::size_t pos, std::size_t count,
            const my_string& str, std::size_t pos_str,
            std::size_t count_str = npos);
            
    my_string&
    replace(std::size_t pos, std::size_t count,
            const CharT* str, std::size_t count_str);
            
    my_string&
    replace(std::size_t pos, std::size_t count,
            const CharT* str);
            
    my_string&
    replace(std::size_t pos, std::size_t count,
            std::size_t count_ch, CharT ch);
            
    void
    push_back(CharT ch);
    
    void
    pop_back();
    
    my_string&
    append(std::size_t count, CharT ch);
    
    my_string&
    append(const my_string& str);
    
    my_string&
    append(const my_string& str, std::size_t pos,
           std::size_t count = npos);
           
    my_string&
    append(const CharT* str);
    
    my_string&
    append(const CharT* str, std::size_t count);
    
    my_string&
    operator+=(const my_string& str);
    
    my_string&
    operator+=(CharT ch);
    
    my_string&
    operator+=(const CharT* str);
    
    my_string&
    erase(std::size_t index = 0std::size_t count = npos); 
    
    [[nodiscard]] constexpr
    int
    compare(const my_string& str) const noexcept;
    
    [[nodiscard]] constexpr
    int
    compare(std::size_t pos, std::size_t count,
            const my_string& str) const;
            
    [[nodiscard]] constexpr
    int
    compare(std::size_t pos1, std::size_t count1,
            const my_string& str, std::size_t pos2,
            std::size_t count2 = npos) const;
            
    [[nodiscard]] constexpr
    int
    compare(const CharT* str) const;
    
    [[nodiscard]] constexpr
    int
    compare(std::size_t pos, std::size_t count,
            const CharT* str) const;
            
    [[nodiscard]] constexpr
    int
    compare(std::size_t pos, std::size_t count1,
            const CharT* str, std::size_t count2) const;
            
    [[nodiscard]] constexpr
    my_string
    substr(std::size_t pos = 0std::size_t count = npos)
           const;
        
    void
    resize(std::size_t n);
    
    void
    resize(std::size_t n, CharT ch);
    
    void
    swap(my_string& rhs) noexcept;
    
    // *************************************** operations
    
    // search *******************************************
    
    [[nodiscard]] constexpr
    std::size_t
    find(const my_string& str, std::size_t pos = 0)
         const noexcept;
    
    [[nodiscard]] constexpr
    std::size_t
    find(const CharT* str, std::size_t pos = 0const;
    
    [[nodiscard]] constexpr
    std::size_t
    find(const CharT* str, std::size_t pos,
         std::size_t count) const;
         
    [[nodiscard]] constexpr
    std::size_t
    find(CharT ch, std::size_t pos = 0)
         const noexcept;
    
    // ******************************************* search
    
private:
    // fixed capacity construction **********************
    
    my_string(std::size_t cap, const my_string& other);
    
    // ********************** fixed capacity construction
    
    // helper functions *********************************
    
    std::size_t
    _safe_cap() noexcept;
    
    void
    _set_sz(std::size_t n) noexcept;
    
    void
    _mutate(std::size_t pos, std::size_t len1,
            const CharT* str, std::size_t len2);
            
    void
    _mutate(std::size_t pos, std::size_t len1, CharT ch,
            std::size_t count);
    
    // ********************************* helper functions
 
    // data members *************************************
    
    std::size_t sz;
    std::size_t cap;
    CharT* dat;
    
    // ************************************* data members
};
 
template <class CharT>
[[nodiscard]] constexpr
my_string<CharT>
operator+(const my_string<CharT>& lhs,
          const my_string<CharT>& rhs);
 
template <class CharT>
[[nodiscard]] constexpr
my_string<CharT>
operator+(const my_string<CharT>& lhs, const CharT* rhs);
 
template <class CharT>
[[nodiscard]] constexpr
my_string<CharT>
operator+(const CharT* lhs, const my_string<CharT>& rhs);
 
template <class CharT>
[[nodiscard]] constexpr
my_string<CharT>
operator+(const my_string<CharT>& lhs, CharT rhs);
 
template <class CharT>
[[nodiscard]] constexpr
my_string<CharT>
operator+(CharT lhs, const my_string<CharT>& rhs);
 
template <class CharT>
[[nodiscard]] constexpr
my_string<CharT>
operator+(my_string<CharT>&& lhs, my_string<CharT>&& rhs);
 
template <class CharT>
[[nodiscard]] constexpr
my_string<CharT>
operator+(my_string<CharT>&& lhs,
          const my_string<CharT>& rhs);
 
template <class CharT>
[[nodiscard]] constexpr
my_string<CharT>
operator+(const my_string<CharT>& lhs,
          my_string<CharT>&& rhs);
 
template <class CharT>
[[nodiscard]] constexpr
my_string<CharT>
operator+(my_string<CharT>&& lhs, const CharT* rhs);
 
template <class CharT>
[[nodiscard]] constexpr
my_string<CharT>
operator+(const CharT* lhs, my_string<CharT>&& rhs);
 
template <class CharT>
[[nodiscard]] constexpr
my_string<CharT>
operator+(my_string<CharT>&& lhs, CharT rhs);
 
template <class CharT>
[[nodiscard]] constexpr
my_string<CharT>
operator+(CharT lhs, my_string<CharT>&& rhs);
 
template <class CharT>
[[nodiscard]] constexpr
bool
operator==(const my_string<CharT>& lhs,
           const my_string<CharT>& rhs) noexcept;
 
template <class CharT>
[[nodiscard]] constexpr
bool
operator==(const CharT* lhs, const my_string<CharT>& rhs);
 
template <class CharT>
[[nodiscard]] constexpr
bool
operator==(const my_string<CharT>& lhs, const CharT* rhs);
 
template <class CharT>
[[nodiscard]] constexpr
bool
operator!=(const my_string<CharT>& lhs,
           const my_string<CharT>& rhs) noexcept;
 
template <class CharT>
[[nodiscard]] constexpr
bool
operator!=(const CharT* lhs, const my_string<CharT>& rhs);
 
template <class CharT>
[[nodiscard]] constexpr
bool
operator!=(const my_string<CharT>& lhs, const CharT* rhs);
 
template <class CharT>
[[nodiscard]] constexpr
bool
operator<(const my_string<CharT>& lhs,
          const my_string<CharT>& rhs) noexcept;
 
template <class CharT>
[[nodiscard]] constexpr
bool
operator<(const CharT* lhs, const my_string<CharT>& rhs);
 
template <class CharT>
[[nodiscard]] constexpr
bool
operator<(const my_string<CharT>& lhs, const CharT* rhs);
 
template <class CharT>
[[nodiscard]] constexpr
bool
operator>(const my_string<CharT>& lhs,
          const my_string<CharT>& rhs) noexcept;
 
template <class CharT>
[[nodiscard]] constexpr
bool
operator>(const CharT* lhs, const my_string<CharT>& rhs);
 
template <class CharT>
[[nodiscard]] constexpr
bool
operator>(const my_string<CharT>& lhs, const CharT* rhs);
 
template <class CharT>
[[nodiscard]] constexpr
bool
operator<=(const my_string<CharT>& lhs,
           const my_string<CharT>& rhs) noexcept;
 
template <class CharT>
[[nodiscard]] constexpr
bool
operator<=(const CharT* lhs, const my_string<CharT>& rhs);
 
template <class CharT>
[[nodiscard]] constexpr
bool
operator<=(const my_string<CharT>& lhs, const CharT* rhs);
 
template <class CharT>
[[nodiscard]] constexpr
bool
operator>=(const my_string<CharT>& lhs,
           const my_string<CharT>& rhs) noexcept;
 
template <class CharT>
[[nodiscard]] constexpr
bool
operator>=(const CharT* lhs, const my_string<CharT>& rhs);
 
template <class CharT>
[[nodiscard]] constexpr
bool operator>=(const my_string<CharT>& lhs, const CharT* rhs);
 
template<class CharT, class Traits>
std::basic_ostream<CharT, Traits>&
operator<<(std::basic_ostream<CharT, Traits>& os,
           const my_string<CharT>& str);
 
template <class CharT>
[[nodiscard]] constexpr
std::size_t
_strlen(const CharT* str);
 
template <class CharT>
[[nodiscard]] constexpr
int
_strcmp(const CharT* lhs, const CharT* rhs);
 
template <class CharT>
[[nodiscard]] constexpr
int
_strncmp(const CharT* lhs, const CharT* rhs, std::size_t len);
 
template <class CharT>
[[nodiscard]]
my_string<CharT>
__str_concat(const CharT* lhs, std::size_t len1, const CharT* rhs, std::size_t len2);
 
template <class T, class ... Args>
[[nodiscard]]
T*
_construct(std::size_t num_of_obj, Args&& ... args);
 
template <class T>
void
_destroy(T* ptr);
    
[[nodiscard]] constexpr
std::size_t
closest_bin(std::size_t n) noexcept;
 
#include "my_string.inl"
 
#endif
cs
  • my_string.inl
#include "my_string.h"
 
template <class CharT>
constexpr
my_string<CharT>::
my_string() noexcept
    : sz{ std::size_t{ 0 } }, cap{ std::size_t{ 0 } },
      dat{ nullptr }
{
    
}
 
template <class CharT>
my_string<CharT>::
my_string(std::size_t count, CharT ch)
    : sz{ count }, cap{ closest_bin(sz) },
      dat{ _construct<CharT>(_safe_cap(), CharT{}) }
{
    std::fill_n(this->data(), this->size(), ch);
}
 
template <class CharT>
my_string<CharT>::
my_string(const my_string& other, std::size_t pos,
          std::size_t count)
    : sz{ std::min(count, other.size() -
                   _check_i_is_in_size(pos, other,
                                       "pos > other.size()")) },
      cap{ closest_bin(sz) },
      dat{ _construct<CharT>(_safe_cap(), CharT{}) }
{
    std::copy(other.data() + pos,
              other.data() + pos + this->size(),
              this->data());
}
 
template <class CharT>
my_string<CharT>::
my_string(const CharT* str, std::size_t count)
    : sz{ count }, cap{ closest_bin(sz) },
      dat{ _construct<CharT>(_safe_cap(), CharT{}) }
{
    std::copy(str, str + this->size(), this->data());
}
 
template <class CharT>
my_string<CharT>::
my_string(const CharT* str)
    : my_string(str, _strlen(str))
{
 
}
 
template <class CharT>
my_string<CharT>::
my_string(const my_string& other)
    : my_string(other, std::size_t{ 0 }, npos)
{
 
}
 
template <class CharT>
my_string<CharT>::my_string(my_string&& other) noexcept
    : my_string()
{
    this->swap(other);
}
 
template <class CharT>
my_string<CharT>::~my_string()
{
    _destroy(data());
}
 
template <class CharT>
my_string<CharT>&
my_string<CharT>::
operator=(const my_string& other)
{
    return this->assign(other);
}
 
template <class CharT>
my_string<CharT>&
my_string<CharT>::
operator=(my_string&& other) noexcept
{
    return this->assign(std::move(other));
}
 
template <class CharT>
my_string<CharT>&
my_string<CharT>::
operator=(const CharT* str)
{
    return this->assign(str);
}
 
template <class CharT>
my_string<CharT>&
my_string<CharT>::
operator=(CharT ch)
{
    return this->assign(&ch, 1);
}
 
template <class CharT>
my_string<CharT>&
my_string<CharT>::
assign(std::size_t count, CharT ch)
{
    return this->replace(std::size_t{ 0 }, this->size(),
                         count, ch);
}
 
template <class CharT>
my_string<CharT>&
my_string<CharT>::
assign(const my_string& str, std::size_t pos,
       std::size_t count)
{
    return this->replace(std::size_t{ 0 }, this->size(), str,
                         _check_i_is_in_size(pos, str,
                                             "pos > str.size()"),
                         count);
}
 
template <class CharT>
my_string<CharT>&
my_string<CharT>::
assign(my_string&& str) noexcept
{
    this->swap(str);
    
    return *this;
}
 
template <class CharT>
my_string<CharT>&
my_string<CharT>::
assign(const CharT* str, std::size_t count)
{
    return this->replace(std::size_t{ 0 }, this->size(),
                         str, count);
}
 
template <class CharT>
my_string<CharT>&
my_string<CharT>::
assign(const CharT* str)
{
    return this->assign(str, _strlen(str));
}
 
template <class CharT>
constexpr
CharT&
my_string<CharT>::
at(std::size_t pos)
{
    return const_cast<CharT&>(
        static_cast<const my_string&>(*this).at(pos));
}
 
template <class CharT>
constexpr
const CharT&
my_string<CharT>::
at(std::size_t pos) const
{
    return (*this)[
        debug_check_out_of_range(pos, std::size_t{ 0 },
                                 this->size() - 1,
                                 "pos >= size()")];
}
 
template <class CharT>
constexpr
CharT&
my_string<CharT>::
operator[](std::size_t index)
{
    // The behavior is undefined if index > size() : cppreference
    return const_cast<CharT&>(
        static_cast<const my_string&>(*this)[index]);
}
 
template <class CharT>
constexpr
const CharT&
my_string<CharT>::
operator[](std::size_t index) const
{
    return this->data()[index];
}
 
template <class CharT>
constexpr
CharT&
my_string<CharT>::
front()
{
    return const_cast<CharT&>(
        static_cast<const my_string&>(*this).front());
}
 
template <class CharT>
constexpr
const CharT&
my_string<CharT>::
front() const
{
    return (*this)[std::size_t{ 0 }];
}
 
template <class CharT>
constexpr
CharT&
my_string<CharT>::
back()
{
    return const_cast<CharT&>(
        static_cast<const my_string&>(*this).back());
}
 
template <class CharT>
constexpr
const CharT&
my_string<CharT>::
back() const
{
    // The behavior is undefined if empty() == true.
    // : cppreference
    return (*this)[this->size() - 1];
}
 
template <class CharT>
constexpr
CharT*
my_string<CharT>::
data() noexcept
{
    return const_cast<CharT*>(
        static_cast<const my_string&>(*this).data() );
}
 
template <class CharT>
constexpr
const CharT*
my_string<CharT>::
data() const noexcept
{
    return this->dat;
}
 
template <class CharT>
constexpr
const CharT*
my_string<CharT>::
c_str() const noexcept
{
    return this->data();
}
 
template <class CharT>
constexpr
bool
my_string<CharT>::
empty() const noexcept
{
    return this->size() == std::size_t{ 0 };
}
 
template <class CharT>
constexpr
std::size_t
my_string<CharT>::
size() const noexcept
{
    return this->sz;
}
 
template <class CharT>
constexpr
std::size_t
my_string<CharT>::
length() const noexcept
{
    return this->size();
}
 
template <class CharT>
constexpr
std::size_t
my_string<CharT>::
capacity() const noexcept
{
    return this->cap;
}
 
template <class CharT>
void
my_string<CharT>::
reserve(std::size_t new_cap)
{
    if (this->capacity() < new_cap)
        my_string{ new_cap, *this }.swap(*this);
}
 
template <class CharT>
void
my_string<CharT>::
shrink_to_fit()
{
    my_string{ this->size(), *this }.swap(*this);
}
 
template <class CharT>
void
my_string<CharT>::
clear() noexcept
{
    this->_set_sz(std::size_t{ 0 });
}
 
template <class CharT>
my_string<CharT>&
my_string<CharT>::
insert(std::size_t index, std::size_t count, CharT ch)
{
    return this->replace(_check_i_is_in_size(index, *this,
                                             "index > size()"),
                         std::size_t{ 0 }, ch, count);
}
    
template <class CharT>
my_string<CharT>&
my_string<CharT>::
insert(std::size_t index, const CharT* str)
{
    return this->insert(index, str, _strlen(str));
}   
 
template <class CharT>
my_string<CharT>&
my_string<CharT>::
insert(std::size_t index, const CharT* str, std::size_t count)
{
    return this->replace(_check_i_is_in_size(index, *this,
                                             "index > size()"),
                         std::size_t{ 0 }, str, count);
}   
 
template <class CharT>
my_string<CharT>&
my_string<CharT>::
insert(std::size_t index, const my_string& str)
{
    return this->insert(index, str, std::size_t{ 0 });
}
 
template <class CharT>
my_string<CharT>&
my_string<CharT>::
insert(std::size_t index, const my_string& str,
       std::size_t index_str, std::size_t count)
{
    return this->insert(_check_i_is_in_size(index, str,
                                            "index > str.size()"),
                        str.data() + index_str,
                        std::min(count, str.size() - index_str));
}
 
template <class CharT>
my_string<CharT>&
my_string<CharT>::
replace(std::size_t pos, std::size_t count, const my_string& str)
{
    return this->replace(pos, count, str, std::size_t{ 0 });
}
 
template <class CharT>
my_string<CharT>&
my_string<CharT>::
replace(std::size_t pos, std::size_t count, const my_string& str,
        std::size_t pos_str, std::size_t count_str)
{
    // if count_str == npos or if would extend past str.size(),
    // [pos_str, str.size()) is used to replace.
    return this->replace(pos, count, str.data() + pos_str,
                         std::min(count_str, str.size() -
                                  _check_i_is_in_size(pos_str,
                                                      str,
                                                      "pos_str > str.size()")));
}
 
template <class CharT>
my_string<CharT>&
my_string<CharT>::
replace(std::size_t pos, std::size_t count, const CharT* str,
        std::size_t count_str)
{
    this->_mutate(_check_i_is_in_size(pos, *this,
                                      "pos > size()"),
                  count, str, count_str);
    
    return *this;
}
 
template <class CharT>
my_string<CharT>&
my_string<CharT>::
replace(std::size_t pos, std::size_t count, const CharT* str)
{
    return this->replace(pos, count, str, _strlen(str));
}
 
template <class CharT>
my_string<CharT>&
my_string<CharT>::
replace(std::size_t pos, std::size_t count,
        std::size_t count_ch, CharT ch)
{
    this->_mutate(_check_i_is_in_size(pos, *this"pos > size()"),
                  count, ch, count_ch);
    
    return *this;
}
 
template <class CharT>
void
my_string<CharT>::
push_back(CharT ch)
{
    this->_mutate(this->size(), std::size_t{ 0 },
                  ch, std::size_t{ 1 });
}
 
template <class CharT>
void
my_string<CharT>::
pop_back()
{
    this->_mutate(this->size() - 1std::size_t{ 1 },
                  nullptr, std::size_t{ 0 });
}
 
template <class CharT>
my_string<CharT>&
my_string<CharT>::
append(std::size_t count, CharT ch)
{
    return this->insert(this->size(), count, ch);
}   
 
template <class CharT>
my_string<CharT>&
my_string<CharT>::
append(const my_string& str)
{
    return this->append(str, std::size_t{ 0 });
}   
 
template <class CharT>
my_string<CharT>&
my_string<CharT>::
append(const my_string& str, std::size_t pos,
       std::size_t count)
{
    return this->append(str.data() +
                        _check_i_is_in_size(pos, str,
                                            "pos > str.size()"),
                        std::min(count, str.size() - pos));
}   
 
template <class CharT>
my_string<CharT>&
my_string<CharT>::
append(const CharT* str)
{
    return this->append(str, _strlen(str));
}   
 
template <class CharT>
my_string<CharT>&
my_string<CharT>::
append(const CharT* str, std::size_t count)
{
    return this->insert(this->size(), str, count);
}
 
template <class CharT>
my_string<CharT>&
my_string<CharT>::
operator+=(const my_string& str)
{
    return this->append(str);
}
 
template <class CharT>
my_string<CharT>&
my_string<CharT>::
operator+=(CharT ch)
{
    return this->push_back(ch);
}
 
template <class CharT>
my_string<CharT>&
my_string<CharT>::
operator+=(const CharT* str)
{
    return this->append(str);
}
 
template <class CharT>
my_string<CharT>&
my_string<CharT>::
erase(std::size_t index, std::size_t count)
{
    this->_mutate(_check_i_is_in_size(index, *this,
                                      "index > size()"),
                  std::min(count, this->size() - index),
                  nullptr, std::size_t{ 0 });
    
    return *this;
}
 
template <class CharT>
constexpr
int
my_string<CharT>::
compare(const my_string& str) const noexcept
{
    return this->compare(std::size_t{ 0 }, this->size(), str);
}
 
template <class CharT>
constexpr
int
my_string<CharT>::
compare(std::size_t pos, std::size_t count,
        const my_string& str) const
{
    return this->compare(pos, count, str, std::size_t{ 0 });
}
 
template <class CharT>
constexpr
int
my_string<CharT>::
compare(std::size_t pos1, std::size_t count1,
        const my_string& str, std::size_t pos2,
        std::size_t count2) const
{
    return this->compare(_check_i_is_in_size(pos1, *this,
                                             "pos1 > size()"),
                         count1, str.data() +
                         _check_i_is_in_size(pos2, str,
                                             "pos2 > str.size()"),
                         count2);
}
 
template <class CharT>
constexpr
int
my_string<CharT>::
compare(const CharT* str) const
{
    return this->compare(std::size_t{ 0 }, this->size(),
                         str);
}
 
template <class CharT>
constexpr
int
my_string<CharT>::
compare(std::size_t pos, std::size_t count,
        const CharT* str) const
{
    return this->compare(std::size_t{ 0 }, count, str,
                         _strlen(str));
}
 
template <class CharT>
constexpr
int
my_string<CharT>::
compare(std::size_t pos, std::size_t count1,
        const CharT* str, std::size_t count2) const
{
    auto my_len = std::min(count1, this->size() -
                           _check_i_is_in_size(pos, *this,
                                               "pos > size()"));
    
    // Firstly, compare characters in common range
    int ret = _strncmp(this->data() + pos, str,
                       std::min(my_len, count2));
    
    if (!ret && my_len)
        // Secondly, compare lengths
        ret = my_len - count2;
        
    return ret;
}
 
template <class CharT>
constexpr
my_string<CharT>
my_string<CharT>::
substr(std::size_t pos, std::size_t count) const
{
    return my_string{*this,
                    _check_i_is_in_size(pos, *this"pos > size()"),
                    count};
}
 
template <class CharT>
void
my_string<CharT>::
resize(std::size_t n)
{
    if (this->capacity() < n) this->reserve(n);
    
    _set_sz(n);
}
 
template <class CharT>
void
my_string<CharT>::
resize(std::size_t n, CharT ch)
{
    if (this->capacity() < n)
    {
        auto old_size = this->size();
        this->reserve(n);
        
        std::fill_n(this->data() + old_size,
                    this->size() - old_size, ch);
    }
    
    _set_sz(n);
}
 
template <class CharT>
void
my_string<CharT>::
swap(my_string& rhs) noexcept
{
    std::swap(this->sz, rhs.sz);
    std::swap(this->cap, rhs.cap);
    std::swap(this->dat, rhs.dat);
}
 
template <class CharT>
constexpr
std::size_t
my_string<CharT>::
find(const my_string& str, std::size_t pos) const noexcept
{
    return this->find(str.data(), pos, str.size());
}
 
template <class CharT>
constexpr
std::size_t
my_string<CharT>::
find(const CharT* str, std::size_t pos) const
{
    return this->find(str, pos, _strlen(str));
}
 
template <class CharT>
constexpr
std::size_t
my_string<CharT>::
find(const CharT* str, std::size_t pos,
     std::size_t count) const
{
    std::size_t ret =
        std::search(this->data() + std::min(this->size(), pos),
                    this->data() + this->size(), str, str + count)
        - this->data();
    
    if (ret == this->size()) ret = npos;
    
    return ret;
}
 
template <class CharT>
constexpr
std::size_t
my_string<CharT>::
find(CharT ch, std::size_t pos) const noexcept
{
    std::size_t ret =
        std::find(this->data() + std::min(this->size(), pos),
                  this->data() + this->size(), ch)
        - this->data();
        
    if (ret == this->size()) ret = npos;
    
    return ret;
}
 
template <class CharT>
my_string<CharT>::
my_string(std::size_t required_cap, const my_string& other)
    : sz{ std::min(other.sz, required_cap) },
      cap{ required_cap }, dat{ _construct<CharT>(_safe_cap(), CharT{}) }
{
    std::copy(other.data(), other.data() + this->size(),
              this->data());
}
 
template <class CharT>
std::size_t
my_string<CharT>::
_safe_cap() noexcept
{
    return capacity() + 1;
}
 
template <class CharT>
void
my_string<CharT>::
_set_sz(std::size_t n) noexcept
{
    if (data())
    {
        this->sz = n;
        (*this)[n] = CharT{};
    }
}
 
/**
 * data in range [this->data() + pos, this->data() + pos + len1]
 * is replaced with [str, str + len2].
 * exception unchecked.
*/
template <class CharT>
void
my_string<CharT>::
_mutate(std::size_t pos, std::size_t len1,
        const CharT* str, std::size_t len2)
{
    const std::size_t how_much = this->size() - pos - len1;
    const std::size_t new_sz = this->size() + len2 - len1;
    
    if (new_sz > this->capacity())
    {
        my_string tmp{ closest_bin(new_sz), *this };
        
        if (pos)
            std::copy(this->data(), this->data() + pos,
                      tmp.data());
        if (str && len2)
            std::copy(str, str + len2, tmp.data() + pos);
        if (how_much)
            std::copy(this->data() + pos + len1,
                      this->data() + this->size(),
                      tmp.data() + pos + len2);
            
        this->swap(tmp);
    }
    else
    {
        if (how_much)
            std::move(this->data() + pos + len1,
                      this->data() + this->size(),
                      this->data() + pos + len2);
        if (str && len2)
            std::copy(str, str + len2, this->data() + pos);
    }
    
    this->_set_sz(new_sz);
}
 
/**
 * data in range [this->data() + pos, this->data() + pos + len]
 * is replaced with ch of count.
 * exception unchecked.
*/
template <class CharT>
void
my_string<CharT>::
_mutate(std::size_t pos, std::size_t len, CharT ch,
        std::size_t count)
{
    const std::size_t how_much = this->size() - pos - len;
    const std::size_t new_sz = this->size() + count - len;
    
    if (new_sz > this->capacity())
    {
        my_string tmp{ count, ch };
        
        if (pos)
            std::copy(this->data(), this->data() + pos,
                      tmp.data());
        if (count)
            std::fill_n(tmp.data() + pos, count, ch);
        if (how_much)
            std::copy(this->data() + pos + len,
                      this->data() + this->size(),
                      tmp.data() + pos + count);
            
        this->swap(tmp);
    }
    else
    {
        if (count)
            std::fill_n(this->data() + pos, count, ch);
        if (how_much)
            std::move(this->data() + pos + len,
                      this->data() + this->size(),
                      this->data() + pos + count);
    }
    
    this->_set_sz(new_sz);
}
 
template <class CharT>
constexpr
my_string<CharT>
operator+(const my_string<CharT>& lhs,
          const my_string<CharT>& rhs)
{
    return __str_concat(lhs.data(), lhs.size(), rhs.data(), rhs.size());
}
 
template <class CharT>
constexpr
my_string<CharT>
operator+(const my_string<CharT>& lhs, const CharT* rhs)
{
    return __str_concat(lhs.data(), lhs.size(), rhs, _strlen(rhs));
}
 
template <class CharT>
constexpr
my_string<CharT>
operator+(const CharT* lhs, const my_string<CharT>& rhs)
{
    return __str_concat(lhs, _strlen(lhs), rhs.data(), rhs.size());
}
 
template <class CharT>
constexpr
my_string<CharT>
operator+(const my_string<CharT>& lhs, CharT rhs)
{
    return __str_concat(lhs.data(), lhs.size(), &rhs, std::size_t{ 1 });
}
 
template <class CharT>
constexpr
my_string<CharT>
operator+(CharT lhs, const my_string<CharT>& rhs)
{
    return __str_concat(&lhs, std::size_t{ 1 }, rhs.data(), rhs.size());
}
 
template <class CharT>
constexpr
my_string<CharT>
operator+(my_string<CharT>&& lhs, my_string<CharT>&& rhs)
{
    return std::move(lhs.append(rhs));
}
 
template <class CharT>
constexpr
my_string<CharT>
operator+(my_string<CharT>&& lhs, const my_string<CharT>& rhs)
{
    return std::move(lhs.append(rhs));
}
 
template <class CharT>
constexpr
my_string<CharT>
operator+(const my_string<CharT>& lhs, my_string<CharT>&& rhs)
{
    return std::move(rhs.insert(std::size_t{ 0 }, lhs));
}
 
template <class CharT>
constexpr
my_string<CharT>
operator+(my_string<CharT>&& lhs, const CharT* rhs)
{
    return std::move(lhs.append(rhs));
}
 
template <class CharT>
constexpr
my_string<CharT>
operator+(const CharT* lhs, my_string<CharT>&& rhs)
{
    return std::move(rhs.insert(std::size_t{ 0 }, lhs));
}
 
template <class CharT>
constexpr
my_string<CharT>
operator+(my_string<CharT>&& lhs, CharT rhs)
{
    return std::move(lhs.append(std::size_t{ 1 }, rhs));
}
 
template <class CharT>
constexpr
my_string<CharT>
operator+(CharT lhs, my_string<CharT>&& rhs)
{
    return std::move(rhs.insert(std::size_t{ 0 }, std::size_t{ 1 }, lhs));
}
 
template <class CharT>
constexpr
bool
operator==(const my_string<CharT>& lhs,
           const my_string<CharT>& rhs) noexcept
{
    return lhs.size() == rhs.size()
        && !_strncmp(lhs.data(), rhs.data(), lhs.size());
}
 
template <class CharT>
constexpr
bool
operator==(const CharT* lhs, const my_string<CharT>& rhs)
{
    return _strcmp(lhs, rhs.data());
}
 
template <class CharT>
constexpr
bool
operator==(const my_string<CharT>& lhs, const CharT* rhs)
{
    return rhs == lhs;
}
 
template <class CharT>
constexpr
bool
operator!=(const my_string<CharT>& lhs,
           const my_string<CharT>& rhs) noexcept
{
    return !(lhs == rhs);
}
 
template <class CharT>
constexpr
bool
operator!=(const CharT* lhs, const my_string<CharT>& rhs)
{
    return !(lhs == rhs);
}
 
template <class CharT>
constexpr
bool
operator!=(const my_string<CharT>& lhs, const CharT* rhs)
{
    return !(lhs == rhs);
}
 
template <class CharT>
constexpr
bool
operator<(const my_string<CharT>& lhs,
          const my_string<CharT>& rhs) noexcept
{
    return lhs.compare(rhs) < 0;
}
 
template <class CharT>
constexpr
bool
operator<(const CharT* lhs, const my_string<CharT>& rhs)
{
    return rhs.compare(lhs) > 0;
}
 
template <class CharT>
constexpr
bool
operator<(const my_string<CharT>& lhs, const CharT* rhs)
{
    return lhs.compare(rhs) < 0;
}
 
template <class CharT>
constexpr
bool
operator>(const my_string<CharT>& lhs,
          const my_string<CharT>& rhs) noexcept
{
    return rhs < lhs;
}
 
template <class CharT>
constexpr
bool
operator>(const CharT* lhs,
          const my_string<CharT>& rhs)
{
    return rhs < lhs;
}
 
template <class CharT>
constexpr
bool
operator>(const my_string<CharT>& lhs, const CharT* rhs)
{
    return rhs < lhs;
}
 
template <class CharT>
constexpr
bool
operator<=(const my_string<CharT>& lhs,
           const my_string<CharT>& rhs) noexcept
{
    return !(rhs < lhs);
}
 
template <class CharT>
constexpr
bool
operator<=(const CharT* lhs, const my_string<CharT>& rhs)
{
    return !(rhs < lhs);
}
 
template <class CharT>
constexpr
bool
operator<=(const my_string<CharT>& lhs, const CharT* rhs)
{
    return !(rhs < lhs);
}
 
template <class CharT>
constexpr
bool
operator>=(const my_string<CharT>& lhs,
           const my_string<CharT>& rhs) noexcept
{
    return !(lhs < rhs);
}
 
template <class CharT>
constexpr
bool
operator>=(const CharT* lhs, const my_string<CharT>& rhs)
{
    return !(lhs < rhs);
}
 
template <class CharT>
constexpr
bool
operator>=(const my_string<CharT>& lhs, const CharT* rhs)
{
    return !(lhs < rhs);
}
 
template<class CharT, class Traits>
std::basic_ostream<CharT, Traits>&
operator<<(std::basic_ostream<CharT, Traits>& os,
           const my_string<CharT>& str)
{
    return os.write(reinterpret_cast<const char*>(str.data()), str.size());
}
 
template<class CharT, class Traits>
std::basic_istream<CharT, Traits>&
operator>>(std::basic_istream<CharT, Traits>& is,
           my_string<CharT>& str)
{
    static constexpr std::size_t str_max_size = 1000000000;
    
    using istream_type = std::basic_istream<CharT, Traits>;
    using string_type = my_string<CharT>;
    using ios_base = typename istream_type::ios_base;
    using int_type = typename istream_type::int_type;
    using ctype = std::ctype<CharT>;
    using ctype_base = typename ctype::ctype_base;
 
    std::size_t extracted = 0;
    
    typename ios_base::iostate err = ios_base::goodbit;
    typename istream_type::sentry cerb(is, false);
    
    if (cerb)
    {
        try
        {
            // Avoid reallocation for common case.
            str.clear();
            CharT buf[128];
            std::size_t len = 0;          
            const std::streamsize width = is.width();
            const std::size_t n = width > 0 ? width : str_max_size;
            
            const ctype& ct = std::use_facet<ctype>(is.getloc());
            const int_type eof = Traits::eof();
            int_type c = is.rdbuf()->sgetc();
            
            while (extracted < n
                   && !Traits::eq_int_type(c, eof)
                   && !ct.is(ctype_base::space,
                   Traits::to_char_type(c)))
            {
                if (len == sizeof(buf) / sizeof(CharT))
                {
                    str.append(buf, sizeof(buf) / sizeof(CharT));
                    len = 0;
                }
                       
                buf[len++= Traits::to_char_type(c);
                ++extracted;
                c = is.rdbuf()->snextc();
            }
            
            str.append(buf, len);
            
            if (extracted < n && Traits::eq_int_type(c, eof))
                err |= ios_base::eofbit;
                
            is.width(0);
        }
        catch(...)
        {
            is.setstate(ios_base::badbit);
        }
    }
    
    if (!extracted)
        err |= ios_base::failbit;
    if (err)
        is.setstate(err);
    
    return is;
}
 
template <class CharT>
constexpr
std::size_t
_strlen(const CharT* str)
{
    std::size_t ret{ 0 };
    for (; *str != CharT{} ; ++ret, ++str)
        ;
    return ret;
}
 
template <class CharT>
constexpr
int
_strcmp(const CharT* lhs, const CharT* rhs)
{
    for(; *lhs && *lhs == *rhs; ++lhs, ++rhs)
        ;
        
    return *reinterpret_cast<const unsigned*>(lhs)
        - *reinterpret_cast<const unsigned*>(rhs);
}
 
template <class CharT>
constexpr
int
_strncmp(const CharT* lhs, const CharT* rhs, std::size_t n)
{
    for(; n && *lhs && *lhs == *rhs; --n, ++lhs, ++rhs)
        ;
        
    return *reinterpret_cast<const unsigned*>(lhs)
        - *reinterpret_cast<const unsigned*>(rhs);
}
 
template <class CharT>
my_string<CharT>
__str_concat(const CharT* lhs, std::size_t len1,
             const CharT* rhs, std::size_t len2)
{
    my_string<CharT> ret;
    ret.reserve(len1 + len2);
    ret.append(lhs, len1);
    ret.append(rhs, len2);
    
    return ret;
}
 
template <class T, class ... Args>
T*
_construct(std::size_t num_of_obj, Args&& ... args)
{
    return new T[num_of_obj]{ std::forward<Args>(args)... };
}
 
template <class T>
void
_destroy(T* ptr)
{
    delete[] ptr;
}
 
constexpr
std::size_t
closest_bin(std::size_t n) noexcept
{
    std::size_t ret{ n };
    
    if (n >= 2u)
    {
        --n;
        ret = 2u;
        
        while(n >>= 1u)
            ret <<= 1u;
    }
    
    return ret;
}
cs
  • my_exception.h
// if this header is included by a source,
// undef_macros.inl also should be included in the end of the source.
 
/**
 * @brief   checks if a value is in a specific range when _DEBUG macro is defined
 * @param   val     the value which will be checked
 * @param   lo      lower limit of the range
 * @param   hi      higher limit of the range
 * @param   expr    expression which will be forwarded to std::out_of_range
 * 
 * if val is not in [lo, hi] then throws std::out_of_range with expr
*/
#ifndef debug_check_out_of_range
 
#include "my_format.h"
#include <algorithm>
#include <type_traits>
#include <stdexcept>
 
    #define _fw(val) std::forward<std::remove_reference_t<decltype(val)>>(val)
 
    #ifdef _DEBUG
        #define debug_check_out_of_range(val, lo, hi, expr) \
            __check_out_of_range(val, lo, hi, __FILE__, __LINE__, __FUNCTION_NAME__, expr)
    #else
        #define debug_check_out_of_range(val, ...) (val)
    #endif
    
template <class T>
const T&
__check_out_of_range(const T& val, const T& lo, const T& hi,
                     const char* file, decltype(__LINE__) line,
                     const char* func, const char* expr)
{
    if (std::clamp(val, lo, hi) != val)
        throw std::out_of_range{_FMT("in %s:%d:%s:\n%s\n", file, line, func, expr)};
    return val;
}
    
#endif
cs
  • my_format.h
#ifndef __FUNCTION_NAME__
    
    #ifdef __GNUC__     // if compiled by gcc/g++ use __PRETTY_FUNCTION__ for function name
        #define __FUNCTION_NAME__ __PRETTY_FUNCTION__
    #elif _MSC_VER      // elif compiled by msvc use __FUNCSIG__ for function name
        #define __FUNCTION_NAME__ __FUNCSIG__
    #else   // else use __FUNCTION__(standard) for function name
        #define __FUNCTION_NAME__ __FUNCTION__
    #endif
    
#endif
 
// _FMT changes a formatted expression into a string(char*)
#ifndef _FMT
 
    #include <vector>
    
    #ifdef _MSC_VER
        #define MINIGINE_FORMAT_PRINTF _snprintf
    #else
        #define MINIGINE_FORMAT_PRINTF snprintf
    #endif
    
    #define _FMT(format, ...) \
    ([&]() \
    { \
        std::size_t size = MINIGINE_FORMAT_PRINTF(nullptr, 0, format, __VA_ARGS__) + 1; \
        std::vector<char> buf( size );\
        MINIGINE_FORMAT_PRINTF(buf.data(), size, format, __VA_ARGS__); \
        return buf; \
    }().data())
 
#endif
cs

 

 

참고 자료

Scott Meyers, 『Effective C++』, 곽용재 옮김, (3판, 프로텍미디어, 2015)
Scott Meyers, 『Effective Modern C++』, 류광 옮김, (인사이트, 2015)

joellaity.com

 

libc++’s implementation of std::string

I. Introduction

joellaity.com

gcc

 

GitHub - gcc-mirror/gcc

Contribute to gcc-mirror/gcc development by creating an account on GitHub.

github.com

cppreference

 

std::basic_string - cppreference.com

(1) (2) (since C++17) The class template basic_string stores and manipulates sequences of character-like objects, which are non-array objects of trivial standard-layout type. The class is dependent neither on the character type nor on the nature of operati

en.cppreference.com

 

'Short Coding' 카테고리의 다른 글

C++ Smart Pointer Type Traits 구현하기  (0) 2021.12.17