Base64Encoder() = default; ~Base64Encoder() = default; In general, if you want to explicitly provide special member functions, then explicitly provide them all. Others will advise that if you can avoid defining the special member functions, then do so. [Read more here](https://abseil.io/tips/131). class Base64Encoder { public: // ... no default operations declared ... const std::string encode(const std::string s) const; private: // ... ---- const std::string encode(const std::string s) const; `std::string` requires `<string>` be included. `s` is passed by value to `const`, which incurs an unnecessary copy. Consider using `std::string_view` if you have [tag:c++17]. [Read more here](https://abseil.io/tips/1). std::string encode(const std::string_view s) const; Otherwise, pass by reference to `const`. std::string encode(const std::string& s) const; ---- constexpr static unsigned char get_base_64_char(ulong number_of_char) { return base64_table.at(number_of_char); } `ulong` is not a standard unsigned integer type. If you need a fixed-size integer, consider one of the types from `<cstdint>` (e.g. `std::uint8_t`). For this use-case, I'd just use `std::size_t`. Is `number_of_char` a clear description of what the value represents? Would `index` be clearer? Do you need the bounds checking of `base64_table.at()`? ---- const static unsigned char next_ascii(size_t current_index, const std::string s, size_t length_of_s); The first `const` is unnecessary. `size_t` is not guaranteed by the standard to exist in the global namespace. Use `std::size_t` and include `<cstddef>`. [Read more here](https://stackoverflow.com/a/36596739/3762339). ---- const static size_t MINIMAL_B64_STRING_LENGTH = 4; Consider reserving upper case names for the preprocessor. `constexpr`? ---- std::stringstream encoding; Do we need a `std::stringstream`? We can actually calculate the destination buffer length. For base64 encoding, every 3 octets maps to 4 sextets. To find the encoded length \$m\$, find the total number of octets to be read (integral ceiling) and multiply it by the length of each sextet. $$m = 4 \dot ((n + 2) / 3)$$ ---- while (i < length_of_s) { // if first sextet, ... // else if second sextet, ... // else if third sextet, ... // else must be fourth sextet ... } Instead of cycling through each branch on every loop, consider a modulo approach. Loop through full sextets until you have a partial sextet left at the end (the remainder). Then you can branch based on what you have left. for (auto remaining_sextets = s.size() / 3; remaining_sextets--;) { encoded += /* first sextet masked and shifted */ encoded += /* second sextet masked and shifted */ encoded += /* third sextet masked and shifted */ encoded += /* fourth sextet masked and shifted */ } switch (len % 3) { case 2: encoded += /* first sextet masked and shifted */ encoded += /* second sextet masked and shifted */ encoded += /* third sextet masked and shifted */ encoded += '='; break; case 1: encoded += /* first sextet masked and shifted */ encoded += /* second sextet masked and shifted */ encoded += '='; encoded += '='; break; case 0: break; } return encoded;