Self-made string class

Clash Royale CLAN TAG#URR8PPP
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;
up vote
5
down vote
favorite
Since I've been working with C strings these days, I decided to make a string class from the ground-up just to see how it would go. I wrote all labels in Spanish (my mother language) just for the sake of it. If you have problems understanding it, I'll simply translate them into English
(DISCLOSURE: I'm not thinking about actually using this in production code. I was just trying something new. I think the native std::string class is much better for these things. But if you can tell me how this would perform in production code, it would be of great help!).
My main goal was to made a class that could be edited and used with legacy code that needs a const char* instead of std::string.
What do you think about it? I have some doubts about my implementation: for instance, I don't know if returning the buffer as const char* is safe, or if using static_cast is necessary.
Here is my header file (Cadena.h):
#pragma once
#include <iostream>
namespace David
class Cadena
private:
char* bufer;
size_t tamanio;
size_t obtenerLongitud(const char* cadena)
size_t longitud = 0;
for (longitud = 0; cadena[longitud] != ''; ++longitud);
++longitud;
return longitud;
void borrarBufer()
delete bufer;
bufer = nullptr;
void ajustar(size_t longitud)
char* cadenaTemporal = new char[longitud + 1];
if (bufer != nullptr)
for (size_t i = 0; i < tamanio; ++i)
cadenaTemporal[i] = bufer[i];
borrarBufer();
bufer = cadenaTemporal;
cadenaTemporal = nullptr;
tamanio = longitud + 1;
public:
Cadena(const char* cadena)
tamanio = obtenerLongitud(cadena);
bufer = new char[tamanio];
for (size_t longitud = 0; cadena[longitud] != ''; ++longitud)
bufer[longitud] = cadena[longitud];
bufer[tamanio - 1] = '';
Cadena()
tamanio = 0;
bufer = nullptr;
~Cadena()
borrarBufer();
const char* formaConstante() const
return static_cast<const char*>(bufer);
const size_t len() const
return tamanio - 1;
char& operator (size_t indice)
if (indice < tamanio)
return bufer[indice];
return bufer[tamanio - 1];
void operator = (const char* cadena)
borrarBufer();
tamanio = obtenerLongitud(cadena);
bufer = new char[tamanio];
for (size_t longitud = 0; cadena[longitud] != ''; ++longitud)
bufer[longitud] = cadena[longitud];
bufer[tamanio - 1] = '';
friend std::ostream& operator << (std::ostream& salida, const Cadena& cadena)
for (size_t longitud = 0; cadena.formaConstante()[longitud] != ''; ++longitud)
salida << cadena.formaConstante()[longitud];
return salida;
friend void operator >> (std::istream& entrada, Cadena& cadena)
if(cadena.bufer != nullptr) cadena.borrarBufer();
char letra = ' ';
size_t longitud = 0;
while (letra != 'n')
entrada >> std::noskipws >> letra;
cadena.ajustar(longitud);
cadena.bufer[longitud] = letra;
++longitud;
cadena.bufer[longitud - 1] = '';
;
c++ performance object-oriented strings reinventing-the-wheel
add a comment |Â
up vote
5
down vote
favorite
Since I've been working with C strings these days, I decided to make a string class from the ground-up just to see how it would go. I wrote all labels in Spanish (my mother language) just for the sake of it. If you have problems understanding it, I'll simply translate them into English
(DISCLOSURE: I'm not thinking about actually using this in production code. I was just trying something new. I think the native std::string class is much better for these things. But if you can tell me how this would perform in production code, it would be of great help!).
My main goal was to made a class that could be edited and used with legacy code that needs a const char* instead of std::string.
What do you think about it? I have some doubts about my implementation: for instance, I don't know if returning the buffer as const char* is safe, or if using static_cast is necessary.
Here is my header file (Cadena.h):
#pragma once
#include <iostream>
namespace David
class Cadena
private:
char* bufer;
size_t tamanio;
size_t obtenerLongitud(const char* cadena)
size_t longitud = 0;
for (longitud = 0; cadena[longitud] != ''; ++longitud);
++longitud;
return longitud;
void borrarBufer()
delete bufer;
bufer = nullptr;
void ajustar(size_t longitud)
char* cadenaTemporal = new char[longitud + 1];
if (bufer != nullptr)
for (size_t i = 0; i < tamanio; ++i)
cadenaTemporal[i] = bufer[i];
borrarBufer();
bufer = cadenaTemporal;
cadenaTemporal = nullptr;
tamanio = longitud + 1;
public:
Cadena(const char* cadena)
tamanio = obtenerLongitud(cadena);
bufer = new char[tamanio];
for (size_t longitud = 0; cadena[longitud] != ''; ++longitud)
bufer[longitud] = cadena[longitud];
bufer[tamanio - 1] = '';
Cadena()
tamanio = 0;
bufer = nullptr;
~Cadena()
borrarBufer();
const char* formaConstante() const
return static_cast<const char*>(bufer);
const size_t len() const
return tamanio - 1;
char& operator (size_t indice)
if (indice < tamanio)
return bufer[indice];
return bufer[tamanio - 1];
void operator = (const char* cadena)
borrarBufer();
tamanio = obtenerLongitud(cadena);
bufer = new char[tamanio];
for (size_t longitud = 0; cadena[longitud] != ''; ++longitud)
bufer[longitud] = cadena[longitud];
bufer[tamanio - 1] = '';
friend std::ostream& operator << (std::ostream& salida, const Cadena& cadena)
for (size_t longitud = 0; cadena.formaConstante()[longitud] != ''; ++longitud)
salida << cadena.formaConstante()[longitud];
return salida;
friend void operator >> (std::istream& entrada, Cadena& cadena)
if(cadena.bufer != nullptr) cadena.borrarBufer();
char letra = ' ';
size_t longitud = 0;
while (letra != 'n')
entrada >> std::noskipws >> letra;
cadena.ajustar(longitud);
cadena.bufer[longitud] = letra;
++longitud;
cadena.bufer[longitud - 1] = '';
;
c++ performance object-oriented strings reinventing-the-wheel
It seems that every review is missing to point out one thing: the name of your namespace. I don't think you should've called itDavidbecause that doesn't tell anything precise about what is inside of such namespace.
â Xam
Mar 25 at 6:00
add a comment |Â
up vote
5
down vote
favorite
up vote
5
down vote
favorite
Since I've been working with C strings these days, I decided to make a string class from the ground-up just to see how it would go. I wrote all labels in Spanish (my mother language) just for the sake of it. If you have problems understanding it, I'll simply translate them into English
(DISCLOSURE: I'm not thinking about actually using this in production code. I was just trying something new. I think the native std::string class is much better for these things. But if you can tell me how this would perform in production code, it would be of great help!).
My main goal was to made a class that could be edited and used with legacy code that needs a const char* instead of std::string.
What do you think about it? I have some doubts about my implementation: for instance, I don't know if returning the buffer as const char* is safe, or if using static_cast is necessary.
Here is my header file (Cadena.h):
#pragma once
#include <iostream>
namespace David
class Cadena
private:
char* bufer;
size_t tamanio;
size_t obtenerLongitud(const char* cadena)
size_t longitud = 0;
for (longitud = 0; cadena[longitud] != ''; ++longitud);
++longitud;
return longitud;
void borrarBufer()
delete bufer;
bufer = nullptr;
void ajustar(size_t longitud)
char* cadenaTemporal = new char[longitud + 1];
if (bufer != nullptr)
for (size_t i = 0; i < tamanio; ++i)
cadenaTemporal[i] = bufer[i];
borrarBufer();
bufer = cadenaTemporal;
cadenaTemporal = nullptr;
tamanio = longitud + 1;
public:
Cadena(const char* cadena)
tamanio = obtenerLongitud(cadena);
bufer = new char[tamanio];
for (size_t longitud = 0; cadena[longitud] != ''; ++longitud)
bufer[longitud] = cadena[longitud];
bufer[tamanio - 1] = '';
Cadena()
tamanio = 0;
bufer = nullptr;
~Cadena()
borrarBufer();
const char* formaConstante() const
return static_cast<const char*>(bufer);
const size_t len() const
return tamanio - 1;
char& operator (size_t indice)
if (indice < tamanio)
return bufer[indice];
return bufer[tamanio - 1];
void operator = (const char* cadena)
borrarBufer();
tamanio = obtenerLongitud(cadena);
bufer = new char[tamanio];
for (size_t longitud = 0; cadena[longitud] != ''; ++longitud)
bufer[longitud] = cadena[longitud];
bufer[tamanio - 1] = '';
friend std::ostream& operator << (std::ostream& salida, const Cadena& cadena)
for (size_t longitud = 0; cadena.formaConstante()[longitud] != ''; ++longitud)
salida << cadena.formaConstante()[longitud];
return salida;
friend void operator >> (std::istream& entrada, Cadena& cadena)
if(cadena.bufer != nullptr) cadena.borrarBufer();
char letra = ' ';
size_t longitud = 0;
while (letra != 'n')
entrada >> std::noskipws >> letra;
cadena.ajustar(longitud);
cadena.bufer[longitud] = letra;
++longitud;
cadena.bufer[longitud - 1] = '';
;
c++ performance object-oriented strings reinventing-the-wheel
Since I've been working with C strings these days, I decided to make a string class from the ground-up just to see how it would go. I wrote all labels in Spanish (my mother language) just for the sake of it. If you have problems understanding it, I'll simply translate them into English
(DISCLOSURE: I'm not thinking about actually using this in production code. I was just trying something new. I think the native std::string class is much better for these things. But if you can tell me how this would perform in production code, it would be of great help!).
My main goal was to made a class that could be edited and used with legacy code that needs a const char* instead of std::string.
What do you think about it? I have some doubts about my implementation: for instance, I don't know if returning the buffer as const char* is safe, or if using static_cast is necessary.
Here is my header file (Cadena.h):
#pragma once
#include <iostream>
namespace David
class Cadena
private:
char* bufer;
size_t tamanio;
size_t obtenerLongitud(const char* cadena)
size_t longitud = 0;
for (longitud = 0; cadena[longitud] != ''; ++longitud);
++longitud;
return longitud;
void borrarBufer()
delete bufer;
bufer = nullptr;
void ajustar(size_t longitud)
char* cadenaTemporal = new char[longitud + 1];
if (bufer != nullptr)
for (size_t i = 0; i < tamanio; ++i)
cadenaTemporal[i] = bufer[i];
borrarBufer();
bufer = cadenaTemporal;
cadenaTemporal = nullptr;
tamanio = longitud + 1;
public:
Cadena(const char* cadena)
tamanio = obtenerLongitud(cadena);
bufer = new char[tamanio];
for (size_t longitud = 0; cadena[longitud] != ''; ++longitud)
bufer[longitud] = cadena[longitud];
bufer[tamanio - 1] = '';
Cadena()
tamanio = 0;
bufer = nullptr;
~Cadena()
borrarBufer();
const char* formaConstante() const
return static_cast<const char*>(bufer);
const size_t len() const
return tamanio - 1;
char& operator (size_t indice)
if (indice < tamanio)
return bufer[indice];
return bufer[tamanio - 1];
void operator = (const char* cadena)
borrarBufer();
tamanio = obtenerLongitud(cadena);
bufer = new char[tamanio];
for (size_t longitud = 0; cadena[longitud] != ''; ++longitud)
bufer[longitud] = cadena[longitud];
bufer[tamanio - 1] = '';
friend std::ostream& operator << (std::ostream& salida, const Cadena& cadena)
for (size_t longitud = 0; cadena.formaConstante()[longitud] != ''; ++longitud)
salida << cadena.formaConstante()[longitud];
return salida;
friend void operator >> (std::istream& entrada, Cadena& cadena)
if(cadena.bufer != nullptr) cadena.borrarBufer();
char letra = ' ';
size_t longitud = 0;
while (letra != 'n')
entrada >> std::noskipws >> letra;
cadena.ajustar(longitud);
cadena.bufer[longitud] = letra;
++longitud;
cadena.bufer[longitud - 1] = '';
;
c++ performance object-oriented strings reinventing-the-wheel
edited Mar 24 at 17:57
Daniel
4,1132836
4,1132836
asked Mar 24 at 16:01
David Quintero Granadillo
724
724
It seems that every review is missing to point out one thing: the name of your namespace. I don't think you should've called itDavidbecause that doesn't tell anything precise about what is inside of such namespace.
â Xam
Mar 25 at 6:00
add a comment |Â
It seems that every review is missing to point out one thing: the name of your namespace. I don't think you should've called itDavidbecause that doesn't tell anything precise about what is inside of such namespace.
â Xam
Mar 25 at 6:00
It seems that every review is missing to point out one thing: the name of your namespace. I don't think you should've called it
David because that doesn't tell anything precise about what is inside of such namespace.â Xam
Mar 25 at 6:00
It seems that every review is missing to point out one thing: the name of your namespace. I don't think you should've called it
David because that doesn't tell anything precise about what is inside of such namespace.â Xam
Mar 25 at 6:00
add a comment |Â
3 Answers
3
active
oldest
votes
up vote
8
down vote
accepted
Possible undefined behaviour
There are several places that will lead to undefined behaviour.
In ajustar it can be possible that longitud < taminio. We will then happily copy into cadenaTemporal[i] where longitud < i < taminio. It's easy to create an example for this:
David::Cadena example("oh oh");
std::cin >> example;
So instead of taminio use both longitud as well as taminio as upper limit.
In operator (size_t indice), you return bufer[-1] on example[0] if the string hasn't been initialized. This should either get documented or checked.
Consistency
borrarBufer() should also set tamino to zero. That seems to be your "empty string" scenario.
Don't include the in len()
If the string is empty, len() should return 0, not -1. strlen() or std::string::size() don't return the either.
const size_t len() const
return tamanio;
Use common interface
operator= should return a Cadena&, not void. Similar, operator>> should return istream&, not void.
Follow the rule of five
You provide a custom constructor. Therefore, you should also provide
- a custom copy constructor,
- a custom move constructor,
- a custom copy assignment,
- a custom move assignment and
- a custom destructor.
You don't provide any of them, but you should. For example, I cannot copy a Cadena at the moment. I have to write
Cadena my_copy(other.formaConstante())
Use copy and swap idiom for operator=
As soon as you have a copy constructor, you can write operator= very easy:
void swap(Cadena & other)
std::swap(tamino, other.tamino);
std::swap(bufer, other.bufer);
Cadena& operator=(const char* cadena)
Cadena temporary(cadena);
swap(cadena);
return *this;
Provide push(char) to add single characters
This will make operator>> a lot simpler.
Use a capacity
Your operator>> is $mathcal O(n^2)$ since you have to realloc for every single char. Instead, allocate more memory than you actually need. Or use std::vector for your internal memory instead, it will take care of that automatically.
Thanks for the advice! I need to become acquainted with move semantics.
â David Quintero Granadillo
Mar 24 at 23:37
add a comment |Â
up vote
6
down vote
Not using the standard naming conventions is worse in C++ than in a language without so powerful templates or other liberally-used reflection facilities. It in essence bars your class from interoperation with the standard-library.
Making the 0-terminator part of the string is an interesting choice. As in the curse "may you live in interesting times".
Provide the standard iterator-interface. Doing so enables use of many standard-algorithm and the for-range-loop.
Don't cast if you don't have to. Casting is a way too bypass the compiler enforcing type-safety, and is thus dangerous.
In your case of converting fromchar*toconst char*, an implicit conversion is enough.While it's a valid decision to return the terminator if the index is out of range, that might be a bit costly for marginal utility.
Provide a
const-interface besides a non-constone. Otherwise, being const-correct will be quite hard on your consumers.Streaming each and every single character in the string by itself is contra-indicated, as it means all the setup and error-checking must be done for every one by itself, instead for all of them together. Also, other output may be interleaved.
You didn't provide copy-/move ctor/assignment, though you provide a dtor to free the contained resource. Mind the rule of 5/3/0.
While you can decide not to provide a capacity, consider at least using one in
op>>to avoid quadratic behavior.
add a comment |Â
up vote
6
down vote
Code Review
Pragmas
Not all compilers support this pragma.
#pragma once
Size Vs Capacity
Having only a size dooms you to re-allocating a new buffer every-time text is added to the string. It can be more efficient to allocate a buffer slightly larger than you need (capacity) and only reallocating when size (tamanio) exceeds capacity.
char* bufer;
size_t tamanio;
Size includes the
Sure.
size_t obtenerLongitud(const char* cadena)
size_t longitud = 0;
for (longitud = 0; cadena[longitud] != ''; ++longitud);
++longitud;
return longitud;
But you need to be consistent about making sure that buffer[tamanio] == '' in the rest of the code.
Adjusting the size?
You don't guarantee:
buffer[tamanio] == ''
This new
char* cadenaTemporal = new char[longitud + 1];
Does not null the space before returning it. The content is quite random.
This is completely a waste of time:
cadenaTemporal = nullptr;
Constructor
You don't obey the rule of three.
Bsically if you define one of Destructor/Copy Constructor/Copy Assignment you should probably define all three. In your case you don't define Copy Constructor. As a result you will get a double delete if you every copy your string.
Cadena tmp1("Test");
Cadena tmp2(tmp1); // Compiler generated copy constructor
// Does a simple shallow copy. So both
// objects point at the same buffer.
// Destructor of both objects called here.
// The destructor of `tmp2` first which deletes the buffer,
// then the destructor of `tmp1` which destroys the same buffer.
Operator Vs at()
In C++ we have a guiding principle. You should not have to pay for something you do not use:
char& operator (size_t indice)
if (indice < tamanio)
return bufer[indice];
return bufer[tamanio - 1];
In most cases the indice has already been checked and guranteed to be inside the range [0, tamanio]. Look at the standard use case.
Cadena tmp1("Test");
for(int loop = 0; loop < tmp1.len(); ++loop)
std::cout << tmp1[loop];
We have already guaranteed that the index is in the correct range. So every call to the operator has a check that is not needed. This is why the standard std::string has two methods accessing the buffer. operator which is unchecked and at() which checks the index is in range.
It is questionable if it is useful to return something valid when the index is out of range. Why not throw an exception so the developer knows that something went wrong.
Copy Assignment
Sure it works (and is perfectly valid). But normally we return a reference to the object. So that it can act like a standard type and be used in a chained expression.
You should also look up the copy and swap idiom.
Move Semantics
Since C++11 (seven years ago now) the language has had move semantics. Your class should also support this. It allows for an efficient move of the object. it requires you to define two operators.
Cadena(Cadena&& m) noexcept; // Move constructor
Cadena& operator=(Cadena&&) noexcept; // Move assignement
Input operator
Sure this works.
I would note that it has different semantics to the std::string which imply reads one word. Also remember my note about capacity and size above. This function is very inefficient as it reallocates the buffer after reading every character.
friend void operator >> (std::istream& entrada, Cadena& cadena)
if(cadena.bufer != nullptr) cadena.borrarBufer();
char letra = ' ';
size_t longitud = 0;
while (letra != 'n')
entrada >> std::noskipws >> letra;
cadena.ajustar(longitud);
cadena.bufer[longitud] = letra;
++longitud;
cadena.bufer[longitud - 1] = '';
Thanks for the criticism! As a person just starting out with C++, I appreciate your help.
â David Quintero Granadillo
Mar 24 at 23:42
add a comment |Â
3 Answers
3
active
oldest
votes
3 Answers
3
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
8
down vote
accepted
Possible undefined behaviour
There are several places that will lead to undefined behaviour.
In ajustar it can be possible that longitud < taminio. We will then happily copy into cadenaTemporal[i] where longitud < i < taminio. It's easy to create an example for this:
David::Cadena example("oh oh");
std::cin >> example;
So instead of taminio use both longitud as well as taminio as upper limit.
In operator (size_t indice), you return bufer[-1] on example[0] if the string hasn't been initialized. This should either get documented or checked.
Consistency
borrarBufer() should also set tamino to zero. That seems to be your "empty string" scenario.
Don't include the in len()
If the string is empty, len() should return 0, not -1. strlen() or std::string::size() don't return the either.
const size_t len() const
return tamanio;
Use common interface
operator= should return a Cadena&, not void. Similar, operator>> should return istream&, not void.
Follow the rule of five
You provide a custom constructor. Therefore, you should also provide
- a custom copy constructor,
- a custom move constructor,
- a custom copy assignment,
- a custom move assignment and
- a custom destructor.
You don't provide any of them, but you should. For example, I cannot copy a Cadena at the moment. I have to write
Cadena my_copy(other.formaConstante())
Use copy and swap idiom for operator=
As soon as you have a copy constructor, you can write operator= very easy:
void swap(Cadena & other)
std::swap(tamino, other.tamino);
std::swap(bufer, other.bufer);
Cadena& operator=(const char* cadena)
Cadena temporary(cadena);
swap(cadena);
return *this;
Provide push(char) to add single characters
This will make operator>> a lot simpler.
Use a capacity
Your operator>> is $mathcal O(n^2)$ since you have to realloc for every single char. Instead, allocate more memory than you actually need. Or use std::vector for your internal memory instead, it will take care of that automatically.
Thanks for the advice! I need to become acquainted with move semantics.
â David Quintero Granadillo
Mar 24 at 23:37
add a comment |Â
up vote
8
down vote
accepted
Possible undefined behaviour
There are several places that will lead to undefined behaviour.
In ajustar it can be possible that longitud < taminio. We will then happily copy into cadenaTemporal[i] where longitud < i < taminio. It's easy to create an example for this:
David::Cadena example("oh oh");
std::cin >> example;
So instead of taminio use both longitud as well as taminio as upper limit.
In operator (size_t indice), you return bufer[-1] on example[0] if the string hasn't been initialized. This should either get documented or checked.
Consistency
borrarBufer() should also set tamino to zero. That seems to be your "empty string" scenario.
Don't include the in len()
If the string is empty, len() should return 0, not -1. strlen() or std::string::size() don't return the either.
const size_t len() const
return tamanio;
Use common interface
operator= should return a Cadena&, not void. Similar, operator>> should return istream&, not void.
Follow the rule of five
You provide a custom constructor. Therefore, you should also provide
- a custom copy constructor,
- a custom move constructor,
- a custom copy assignment,
- a custom move assignment and
- a custom destructor.
You don't provide any of them, but you should. For example, I cannot copy a Cadena at the moment. I have to write
Cadena my_copy(other.formaConstante())
Use copy and swap idiom for operator=
As soon as you have a copy constructor, you can write operator= very easy:
void swap(Cadena & other)
std::swap(tamino, other.tamino);
std::swap(bufer, other.bufer);
Cadena& operator=(const char* cadena)
Cadena temporary(cadena);
swap(cadena);
return *this;
Provide push(char) to add single characters
This will make operator>> a lot simpler.
Use a capacity
Your operator>> is $mathcal O(n^2)$ since you have to realloc for every single char. Instead, allocate more memory than you actually need. Or use std::vector for your internal memory instead, it will take care of that automatically.
Thanks for the advice! I need to become acquainted with move semantics.
â David Quintero Granadillo
Mar 24 at 23:37
add a comment |Â
up vote
8
down vote
accepted
up vote
8
down vote
accepted
Possible undefined behaviour
There are several places that will lead to undefined behaviour.
In ajustar it can be possible that longitud < taminio. We will then happily copy into cadenaTemporal[i] where longitud < i < taminio. It's easy to create an example for this:
David::Cadena example("oh oh");
std::cin >> example;
So instead of taminio use both longitud as well as taminio as upper limit.
In operator (size_t indice), you return bufer[-1] on example[0] if the string hasn't been initialized. This should either get documented or checked.
Consistency
borrarBufer() should also set tamino to zero. That seems to be your "empty string" scenario.
Don't include the in len()
If the string is empty, len() should return 0, not -1. strlen() or std::string::size() don't return the either.
const size_t len() const
return tamanio;
Use common interface
operator= should return a Cadena&, not void. Similar, operator>> should return istream&, not void.
Follow the rule of five
You provide a custom constructor. Therefore, you should also provide
- a custom copy constructor,
- a custom move constructor,
- a custom copy assignment,
- a custom move assignment and
- a custom destructor.
You don't provide any of them, but you should. For example, I cannot copy a Cadena at the moment. I have to write
Cadena my_copy(other.formaConstante())
Use copy and swap idiom for operator=
As soon as you have a copy constructor, you can write operator= very easy:
void swap(Cadena & other)
std::swap(tamino, other.tamino);
std::swap(bufer, other.bufer);
Cadena& operator=(const char* cadena)
Cadena temporary(cadena);
swap(cadena);
return *this;
Provide push(char) to add single characters
This will make operator>> a lot simpler.
Use a capacity
Your operator>> is $mathcal O(n^2)$ since you have to realloc for every single char. Instead, allocate more memory than you actually need. Or use std::vector for your internal memory instead, it will take care of that automatically.
Possible undefined behaviour
There are several places that will lead to undefined behaviour.
In ajustar it can be possible that longitud < taminio. We will then happily copy into cadenaTemporal[i] where longitud < i < taminio. It's easy to create an example for this:
David::Cadena example("oh oh");
std::cin >> example;
So instead of taminio use both longitud as well as taminio as upper limit.
In operator (size_t indice), you return bufer[-1] on example[0] if the string hasn't been initialized. This should either get documented or checked.
Consistency
borrarBufer() should also set tamino to zero. That seems to be your "empty string" scenario.
Don't include the in len()
If the string is empty, len() should return 0, not -1. strlen() or std::string::size() don't return the either.
const size_t len() const
return tamanio;
Use common interface
operator= should return a Cadena&, not void. Similar, operator>> should return istream&, not void.
Follow the rule of five
You provide a custom constructor. Therefore, you should also provide
- a custom copy constructor,
- a custom move constructor,
- a custom copy assignment,
- a custom move assignment and
- a custom destructor.
You don't provide any of them, but you should. For example, I cannot copy a Cadena at the moment. I have to write
Cadena my_copy(other.formaConstante())
Use copy and swap idiom for operator=
As soon as you have a copy constructor, you can write operator= very easy:
void swap(Cadena & other)
std::swap(tamino, other.tamino);
std::swap(bufer, other.bufer);
Cadena& operator=(const char* cadena)
Cadena temporary(cadena);
swap(cadena);
return *this;
Provide push(char) to add single characters
This will make operator>> a lot simpler.
Use a capacity
Your operator>> is $mathcal O(n^2)$ since you have to realloc for every single char. Instead, allocate more memory than you actually need. Or use std::vector for your internal memory instead, it will take care of that automatically.
answered Mar 24 at 17:18
Zeta
14.3k23267
14.3k23267
Thanks for the advice! I need to become acquainted with move semantics.
â David Quintero Granadillo
Mar 24 at 23:37
add a comment |Â
Thanks for the advice! I need to become acquainted with move semantics.
â David Quintero Granadillo
Mar 24 at 23:37
Thanks for the advice! I need to become acquainted with move semantics.
â David Quintero Granadillo
Mar 24 at 23:37
Thanks for the advice! I need to become acquainted with move semantics.
â David Quintero Granadillo
Mar 24 at 23:37
add a comment |Â
up vote
6
down vote
Not using the standard naming conventions is worse in C++ than in a language without so powerful templates or other liberally-used reflection facilities. It in essence bars your class from interoperation with the standard-library.
Making the 0-terminator part of the string is an interesting choice. As in the curse "may you live in interesting times".
Provide the standard iterator-interface. Doing so enables use of many standard-algorithm and the for-range-loop.
Don't cast if you don't have to. Casting is a way too bypass the compiler enforcing type-safety, and is thus dangerous.
In your case of converting fromchar*toconst char*, an implicit conversion is enough.While it's a valid decision to return the terminator if the index is out of range, that might be a bit costly for marginal utility.
Provide a
const-interface besides a non-constone. Otherwise, being const-correct will be quite hard on your consumers.Streaming each and every single character in the string by itself is contra-indicated, as it means all the setup and error-checking must be done for every one by itself, instead for all of them together. Also, other output may be interleaved.
You didn't provide copy-/move ctor/assignment, though you provide a dtor to free the contained resource. Mind the rule of 5/3/0.
While you can decide not to provide a capacity, consider at least using one in
op>>to avoid quadratic behavior.
add a comment |Â
up vote
6
down vote
Not using the standard naming conventions is worse in C++ than in a language without so powerful templates or other liberally-used reflection facilities. It in essence bars your class from interoperation with the standard-library.
Making the 0-terminator part of the string is an interesting choice. As in the curse "may you live in interesting times".
Provide the standard iterator-interface. Doing so enables use of many standard-algorithm and the for-range-loop.
Don't cast if you don't have to. Casting is a way too bypass the compiler enforcing type-safety, and is thus dangerous.
In your case of converting fromchar*toconst char*, an implicit conversion is enough.While it's a valid decision to return the terminator if the index is out of range, that might be a bit costly for marginal utility.
Provide a
const-interface besides a non-constone. Otherwise, being const-correct will be quite hard on your consumers.Streaming each and every single character in the string by itself is contra-indicated, as it means all the setup and error-checking must be done for every one by itself, instead for all of them together. Also, other output may be interleaved.
You didn't provide copy-/move ctor/assignment, though you provide a dtor to free the contained resource. Mind the rule of 5/3/0.
While you can decide not to provide a capacity, consider at least using one in
op>>to avoid quadratic behavior.
add a comment |Â
up vote
6
down vote
up vote
6
down vote
Not using the standard naming conventions is worse in C++ than in a language without so powerful templates or other liberally-used reflection facilities. It in essence bars your class from interoperation with the standard-library.
Making the 0-terminator part of the string is an interesting choice. As in the curse "may you live in interesting times".
Provide the standard iterator-interface. Doing so enables use of many standard-algorithm and the for-range-loop.
Don't cast if you don't have to. Casting is a way too bypass the compiler enforcing type-safety, and is thus dangerous.
In your case of converting fromchar*toconst char*, an implicit conversion is enough.While it's a valid decision to return the terminator if the index is out of range, that might be a bit costly for marginal utility.
Provide a
const-interface besides a non-constone. Otherwise, being const-correct will be quite hard on your consumers.Streaming each and every single character in the string by itself is contra-indicated, as it means all the setup and error-checking must be done for every one by itself, instead for all of them together. Also, other output may be interleaved.
You didn't provide copy-/move ctor/assignment, though you provide a dtor to free the contained resource. Mind the rule of 5/3/0.
While you can decide not to provide a capacity, consider at least using one in
op>>to avoid quadratic behavior.
Not using the standard naming conventions is worse in C++ than in a language without so powerful templates or other liberally-used reflection facilities. It in essence bars your class from interoperation with the standard-library.
Making the 0-terminator part of the string is an interesting choice. As in the curse "may you live in interesting times".
Provide the standard iterator-interface. Doing so enables use of many standard-algorithm and the for-range-loop.
Don't cast if you don't have to. Casting is a way too bypass the compiler enforcing type-safety, and is thus dangerous.
In your case of converting fromchar*toconst char*, an implicit conversion is enough.While it's a valid decision to return the terminator if the index is out of range, that might be a bit costly for marginal utility.
Provide a
const-interface besides a non-constone. Otherwise, being const-correct will be quite hard on your consumers.Streaming each and every single character in the string by itself is contra-indicated, as it means all the setup and error-checking must be done for every one by itself, instead for all of them together. Also, other output may be interleaved.
You didn't provide copy-/move ctor/assignment, though you provide a dtor to free the contained resource. Mind the rule of 5/3/0.
While you can decide not to provide a capacity, consider at least using one in
op>>to avoid quadratic behavior.
answered Mar 24 at 19:40
Deduplicator
9,8721844
9,8721844
add a comment |Â
add a comment |Â
up vote
6
down vote
Code Review
Pragmas
Not all compilers support this pragma.
#pragma once
Size Vs Capacity
Having only a size dooms you to re-allocating a new buffer every-time text is added to the string. It can be more efficient to allocate a buffer slightly larger than you need (capacity) and only reallocating when size (tamanio) exceeds capacity.
char* bufer;
size_t tamanio;
Size includes the
Sure.
size_t obtenerLongitud(const char* cadena)
size_t longitud = 0;
for (longitud = 0; cadena[longitud] != ''; ++longitud);
++longitud;
return longitud;
But you need to be consistent about making sure that buffer[tamanio] == '' in the rest of the code.
Adjusting the size?
You don't guarantee:
buffer[tamanio] == ''
This new
char* cadenaTemporal = new char[longitud + 1];
Does not null the space before returning it. The content is quite random.
This is completely a waste of time:
cadenaTemporal = nullptr;
Constructor
You don't obey the rule of three.
Bsically if you define one of Destructor/Copy Constructor/Copy Assignment you should probably define all three. In your case you don't define Copy Constructor. As a result you will get a double delete if you every copy your string.
Cadena tmp1("Test");
Cadena tmp2(tmp1); // Compiler generated copy constructor
// Does a simple shallow copy. So both
// objects point at the same buffer.
// Destructor of both objects called here.
// The destructor of `tmp2` first which deletes the buffer,
// then the destructor of `tmp1` which destroys the same buffer.
Operator Vs at()
In C++ we have a guiding principle. You should not have to pay for something you do not use:
char& operator (size_t indice)
if (indice < tamanio)
return bufer[indice];
return bufer[tamanio - 1];
In most cases the indice has already been checked and guranteed to be inside the range [0, tamanio]. Look at the standard use case.
Cadena tmp1("Test");
for(int loop = 0; loop < tmp1.len(); ++loop)
std::cout << tmp1[loop];
We have already guaranteed that the index is in the correct range. So every call to the operator has a check that is not needed. This is why the standard std::string has two methods accessing the buffer. operator which is unchecked and at() which checks the index is in range.
It is questionable if it is useful to return something valid when the index is out of range. Why not throw an exception so the developer knows that something went wrong.
Copy Assignment
Sure it works (and is perfectly valid). But normally we return a reference to the object. So that it can act like a standard type and be used in a chained expression.
You should also look up the copy and swap idiom.
Move Semantics
Since C++11 (seven years ago now) the language has had move semantics. Your class should also support this. It allows for an efficient move of the object. it requires you to define two operators.
Cadena(Cadena&& m) noexcept; // Move constructor
Cadena& operator=(Cadena&&) noexcept; // Move assignement
Input operator
Sure this works.
I would note that it has different semantics to the std::string which imply reads one word. Also remember my note about capacity and size above. This function is very inefficient as it reallocates the buffer after reading every character.
friend void operator >> (std::istream& entrada, Cadena& cadena)
if(cadena.bufer != nullptr) cadena.borrarBufer();
char letra = ' ';
size_t longitud = 0;
while (letra != 'n')
entrada >> std::noskipws >> letra;
cadena.ajustar(longitud);
cadena.bufer[longitud] = letra;
++longitud;
cadena.bufer[longitud - 1] = '';
Thanks for the criticism! As a person just starting out with C++, I appreciate your help.
â David Quintero Granadillo
Mar 24 at 23:42
add a comment |Â
up vote
6
down vote
Code Review
Pragmas
Not all compilers support this pragma.
#pragma once
Size Vs Capacity
Having only a size dooms you to re-allocating a new buffer every-time text is added to the string. It can be more efficient to allocate a buffer slightly larger than you need (capacity) and only reallocating when size (tamanio) exceeds capacity.
char* bufer;
size_t tamanio;
Size includes the
Sure.
size_t obtenerLongitud(const char* cadena)
size_t longitud = 0;
for (longitud = 0; cadena[longitud] != ''; ++longitud);
++longitud;
return longitud;
But you need to be consistent about making sure that buffer[tamanio] == '' in the rest of the code.
Adjusting the size?
You don't guarantee:
buffer[tamanio] == ''
This new
char* cadenaTemporal = new char[longitud + 1];
Does not null the space before returning it. The content is quite random.
This is completely a waste of time:
cadenaTemporal = nullptr;
Constructor
You don't obey the rule of three.
Bsically if you define one of Destructor/Copy Constructor/Copy Assignment you should probably define all three. In your case you don't define Copy Constructor. As a result you will get a double delete if you every copy your string.
Cadena tmp1("Test");
Cadena tmp2(tmp1); // Compiler generated copy constructor
// Does a simple shallow copy. So both
// objects point at the same buffer.
// Destructor of both objects called here.
// The destructor of `tmp2` first which deletes the buffer,
// then the destructor of `tmp1` which destroys the same buffer.
Operator Vs at()
In C++ we have a guiding principle. You should not have to pay for something you do not use:
char& operator (size_t indice)
if (indice < tamanio)
return bufer[indice];
return bufer[tamanio - 1];
In most cases the indice has already been checked and guranteed to be inside the range [0, tamanio]. Look at the standard use case.
Cadena tmp1("Test");
for(int loop = 0; loop < tmp1.len(); ++loop)
std::cout << tmp1[loop];
We have already guaranteed that the index is in the correct range. So every call to the operator has a check that is not needed. This is why the standard std::string has two methods accessing the buffer. operator which is unchecked and at() which checks the index is in range.
It is questionable if it is useful to return something valid when the index is out of range. Why not throw an exception so the developer knows that something went wrong.
Copy Assignment
Sure it works (and is perfectly valid). But normally we return a reference to the object. So that it can act like a standard type and be used in a chained expression.
You should also look up the copy and swap idiom.
Move Semantics
Since C++11 (seven years ago now) the language has had move semantics. Your class should also support this. It allows for an efficient move of the object. it requires you to define two operators.
Cadena(Cadena&& m) noexcept; // Move constructor
Cadena& operator=(Cadena&&) noexcept; // Move assignement
Input operator
Sure this works.
I would note that it has different semantics to the std::string which imply reads one word. Also remember my note about capacity and size above. This function is very inefficient as it reallocates the buffer after reading every character.
friend void operator >> (std::istream& entrada, Cadena& cadena)
if(cadena.bufer != nullptr) cadena.borrarBufer();
char letra = ' ';
size_t longitud = 0;
while (letra != 'n')
entrada >> std::noskipws >> letra;
cadena.ajustar(longitud);
cadena.bufer[longitud] = letra;
++longitud;
cadena.bufer[longitud - 1] = '';
Thanks for the criticism! As a person just starting out with C++, I appreciate your help.
â David Quintero Granadillo
Mar 24 at 23:42
add a comment |Â
up vote
6
down vote
up vote
6
down vote
Code Review
Pragmas
Not all compilers support this pragma.
#pragma once
Size Vs Capacity
Having only a size dooms you to re-allocating a new buffer every-time text is added to the string. It can be more efficient to allocate a buffer slightly larger than you need (capacity) and only reallocating when size (tamanio) exceeds capacity.
char* bufer;
size_t tamanio;
Size includes the
Sure.
size_t obtenerLongitud(const char* cadena)
size_t longitud = 0;
for (longitud = 0; cadena[longitud] != ''; ++longitud);
++longitud;
return longitud;
But you need to be consistent about making sure that buffer[tamanio] == '' in the rest of the code.
Adjusting the size?
You don't guarantee:
buffer[tamanio] == ''
This new
char* cadenaTemporal = new char[longitud + 1];
Does not null the space before returning it. The content is quite random.
This is completely a waste of time:
cadenaTemporal = nullptr;
Constructor
You don't obey the rule of three.
Bsically if you define one of Destructor/Copy Constructor/Copy Assignment you should probably define all three. In your case you don't define Copy Constructor. As a result you will get a double delete if you every copy your string.
Cadena tmp1("Test");
Cadena tmp2(tmp1); // Compiler generated copy constructor
// Does a simple shallow copy. So both
// objects point at the same buffer.
// Destructor of both objects called here.
// The destructor of `tmp2` first which deletes the buffer,
// then the destructor of `tmp1` which destroys the same buffer.
Operator Vs at()
In C++ we have a guiding principle. You should not have to pay for something you do not use:
char& operator (size_t indice)
if (indice < tamanio)
return bufer[indice];
return bufer[tamanio - 1];
In most cases the indice has already been checked and guranteed to be inside the range [0, tamanio]. Look at the standard use case.
Cadena tmp1("Test");
for(int loop = 0; loop < tmp1.len(); ++loop)
std::cout << tmp1[loop];
We have already guaranteed that the index is in the correct range. So every call to the operator has a check that is not needed. This is why the standard std::string has two methods accessing the buffer. operator which is unchecked and at() which checks the index is in range.
It is questionable if it is useful to return something valid when the index is out of range. Why not throw an exception so the developer knows that something went wrong.
Copy Assignment
Sure it works (and is perfectly valid). But normally we return a reference to the object. So that it can act like a standard type and be used in a chained expression.
You should also look up the copy and swap idiom.
Move Semantics
Since C++11 (seven years ago now) the language has had move semantics. Your class should also support this. It allows for an efficient move of the object. it requires you to define two operators.
Cadena(Cadena&& m) noexcept; // Move constructor
Cadena& operator=(Cadena&&) noexcept; // Move assignement
Input operator
Sure this works.
I would note that it has different semantics to the std::string which imply reads one word. Also remember my note about capacity and size above. This function is very inefficient as it reallocates the buffer after reading every character.
friend void operator >> (std::istream& entrada, Cadena& cadena)
if(cadena.bufer != nullptr) cadena.borrarBufer();
char letra = ' ';
size_t longitud = 0;
while (letra != 'n')
entrada >> std::noskipws >> letra;
cadena.ajustar(longitud);
cadena.bufer[longitud] = letra;
++longitud;
cadena.bufer[longitud - 1] = '';
Code Review
Pragmas
Not all compilers support this pragma.
#pragma once
Size Vs Capacity
Having only a size dooms you to re-allocating a new buffer every-time text is added to the string. It can be more efficient to allocate a buffer slightly larger than you need (capacity) and only reallocating when size (tamanio) exceeds capacity.
char* bufer;
size_t tamanio;
Size includes the
Sure.
size_t obtenerLongitud(const char* cadena)
size_t longitud = 0;
for (longitud = 0; cadena[longitud] != ''; ++longitud);
++longitud;
return longitud;
But you need to be consistent about making sure that buffer[tamanio] == '' in the rest of the code.
Adjusting the size?
You don't guarantee:
buffer[tamanio] == ''
This new
char* cadenaTemporal = new char[longitud + 1];
Does not null the space before returning it. The content is quite random.
This is completely a waste of time:
cadenaTemporal = nullptr;
Constructor
You don't obey the rule of three.
Bsically if you define one of Destructor/Copy Constructor/Copy Assignment you should probably define all three. In your case you don't define Copy Constructor. As a result you will get a double delete if you every copy your string.
Cadena tmp1("Test");
Cadena tmp2(tmp1); // Compiler generated copy constructor
// Does a simple shallow copy. So both
// objects point at the same buffer.
// Destructor of both objects called here.
// The destructor of `tmp2` first which deletes the buffer,
// then the destructor of `tmp1` which destroys the same buffer.
Operator Vs at()
In C++ we have a guiding principle. You should not have to pay for something you do not use:
char& operator (size_t indice)
if (indice < tamanio)
return bufer[indice];
return bufer[tamanio - 1];
In most cases the indice has already been checked and guranteed to be inside the range [0, tamanio]. Look at the standard use case.
Cadena tmp1("Test");
for(int loop = 0; loop < tmp1.len(); ++loop)
std::cout << tmp1[loop];
We have already guaranteed that the index is in the correct range. So every call to the operator has a check that is not needed. This is why the standard std::string has two methods accessing the buffer. operator which is unchecked and at() which checks the index is in range.
It is questionable if it is useful to return something valid when the index is out of range. Why not throw an exception so the developer knows that something went wrong.
Copy Assignment
Sure it works (and is perfectly valid). But normally we return a reference to the object. So that it can act like a standard type and be used in a chained expression.
You should also look up the copy and swap idiom.
Move Semantics
Since C++11 (seven years ago now) the language has had move semantics. Your class should also support this. It allows for an efficient move of the object. it requires you to define two operators.
Cadena(Cadena&& m) noexcept; // Move constructor
Cadena& operator=(Cadena&&) noexcept; // Move assignement
Input operator
Sure this works.
I would note that it has different semantics to the std::string which imply reads one word. Also remember my note about capacity and size above. This function is very inefficient as it reallocates the buffer after reading every character.
friend void operator >> (std::istream& entrada, Cadena& cadena)
if(cadena.bufer != nullptr) cadena.borrarBufer();
char letra = ' ';
size_t longitud = 0;
while (letra != 'n')
entrada >> std::noskipws >> letra;
cadena.ajustar(longitud);
cadena.bufer[longitud] = letra;
++longitud;
cadena.bufer[longitud - 1] = '';
answered Mar 24 at 20:24
Martin York
70.9k481244
70.9k481244
Thanks for the criticism! As a person just starting out with C++, I appreciate your help.
â David Quintero Granadillo
Mar 24 at 23:42
add a comment |Â
Thanks for the criticism! As a person just starting out with C++, I appreciate your help.
â David Quintero Granadillo
Mar 24 at 23:42
Thanks for the criticism! As a person just starting out with C++, I appreciate your help.
â David Quintero Granadillo
Mar 24 at 23:42
Thanks for the criticism! As a person just starting out with C++, I appreciate your help.
â David Quintero Granadillo
Mar 24 at 23:42
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f190379%2fself-made-string-class%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
It seems that every review is missing to point out one thing: the name of your namespace. I don't think you should've called it
Davidbecause that doesn't tell anything precise about what is inside of such namespace.â Xam
Mar 25 at 6:00