Let's consider the following code (live at: http://ideone.com/3Ky4Kr)
#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
class StrStrTest {
public:
StrStrTest(const std::string& ba) {
a = (char*)calloc(1, ba.length() + 1);
strcpy(a, ba.c_str());
}
virtual ~StrStrTest() {
free(a);
}
private:
char* a;
friend std::basic_ostream<char>& operator << (std::basic_ostream<char>& ss, const StrStrTest& a);
friend std::basic_istream<char>& operator >> (std::basic_istream<char>& ss,const StrStrTest& a);
};
std::basic_ostream<char>& operator << (std::basic_ostream<char>& ss, const StrStrTest& a) {
ss << a.a;
return ss;
}
std::basic_istream<char>& operator >> (std::basic_istream<char>& ss,
const StrStrTest& a) {
ss >> a.a; // <<-- HERE
// a.a = NULL;
return ss;
}
int main()
{
StrStrTest bb("foo");
std::cin >> bb;
std::cout << bb;
}
Firstly, why does it compile? On the line marked with <<-- HERE I (subtly) modify a const object. (obviously a.a = NULL; does not compile, that's too obvious).
Secondly does this lead to undefined behaviour?
PS: Please don't consider that the code is not safe, might overwrite memory it does not own, char* vs. std::string, etc... I know these, and it is not the point of the question, this is not production code.
The overload you are using is this: operator>>(std::istream&, char*). It takes a char*, by value. It doesn't modify the pointer (i.e. It doesn't change the pointer to point somewhere else). It modifies the data which the pointer holds the address of, writing a null terminated c-string to that location. So, the const-ness of your object is not being violated, because that data is not part of your object.
If you try to do something like this:
a.a = NULL;
That is modifying a member of your object, and so is not allowed.
Secondly does this lead to undefined behaviour?
It can, if a.a does not point to properly allocated memory with enough space for the next space delimited char data in the stream. But not as a result of anything to do with the const-ness of the StrStrTest object.
Related
I am trying to create a constructor to load a resource from any istream given to it. I cannot seem to figure out the best way to pass the istream parameter into a constructor.
Loader::Loader(istream stream);
This one is obviosly bad due to object slicing, so no option.
Loader::Loader(istream& stream);
This is what I am using now and seems fairly alright. It has one significant issue though - you can't give it a temporary since temporaries cannot bind to non-const references! For example, the following won't work:
Container():
mLoader(ifstream("path/file.txt", ios::binary)
{
}
This is rather a limitation since I am now forced to store the ifstream as a member variable of Container just to extend its lifetime.
Since the problem is with non-const references, one could have though of this:
Loader::Loader(const istream& stream);
But since .seek() etc are non-const, this is not an option either...
So, how can this problem be solved in a neat way?
if your compiler is c++11 or better you can simply provide a version of the constructor that takes the istream as an r-value reference:
void Loader::Loader(std::istream&& is)
quick example:
#include <iostream>
#include <iomanip>
#include <string>
#include <sstream>
struct Loader
{
Loader(std::istream& is)
{
read(is);
}
Loader(std::istream&& is)
{
read(is);
}
void read(std::istream& is)
{
is >> std::quoted(x);
is >> std::quoted(y);
}
std::string x, y;
};
std::ostream& operator<<(std::ostream& os, const Loader& l)
{
os << "x = " << l.x;
os << ", y = " << l.y;
return os;
}
auto main() -> int
{
using namespace std;
Loader l(istringstream(R"text("donkey" "horse")text"));
cout << l << endl;
istringstream not_temp(R"text("apple" "banana")text");
Loader l2(not_temp);
cout << l2 << endl;
return 0;
}
expected output:
x = donkey, y = horse
x = apple, y = banana
The example
Container():
mLoader(ifstream("path/file.txt", ios::binary)
{
}
… is ill-conceived, because the lifetime of the temporary would be just the constructor call. Then you'd be left with a dangling reference. Using that reference would be Undefined Behavior.
However, technically, in order to be able to pass a temporary to a reference-to-non-const formal argument, just define a little helper like this:
template< class Type >
auto temp_ref( Type&& o )
-> Type&
{ return o; }
Then you can call e.g. foo( temp_ref( Whatever() ) ), but just keep in mind that the lifetime of that temporary is the full-expression that it occurs in.
Passing a pointer to a newly created object would work.
Loader(istream * pstream)
{
try
{
// ......
delete pstream;
throw;
}
catch(...)
{
delete pstream;
}
}
Container():
mLoader(new ifstream("path/file.txt", ios::binary))
{
}
Here are the inserts of my code...
main.cpp
void addStuff(Journey& journey)
{
journey.addPerson("John Doe", "USA");
}
void demo()
{
Journey journey("Sweden");
addStuff(journey);
std::cout << journey;
}
int main(int argc, char* argv[])
{
demo();
return 0;
}
Journy.cpp
void Journey::addPerson(const char* name, const char* nationality)
{
add(Person(name, nationality));
}
void Journey::add(Person person)
{
persons_.push_back(person);
}
std::ostream& operator<<(std::ostream& out, const Journey& journey)
{
out << "Journey: " << journey.name_ << std::endl;
out << " Persons attending:" << std::endl;
for(Journey::PersonList::const_iterator person_it = journey.persons_.begin();
person_it != journey.persons_.end();
person_it++)
{
out << " " << *person_it;
}
return out;
}
Person.cpp
Person::Person(){}
Person::Person(const char* name, const char* nationality) : name_(0),
nationality_(0)
{
copyString(&name_, name);
copyString(&nationality_, nationality);
}
Person::Person(const Person& other): name_( other.name_),
nationality_( other.nationality_) {}
void Person::copyString(char** dest, const char* source)
{
unsigned int str_len = strlen(source);
char* str = new char[str_len+1];
strncpy(str, source, str_len);
str[str_len] = '\0';
*dest = str;
}
std::ostream& operator<<(std::ostream& out, const Person& person)
{
out << person.name_ << " (" << person.nationality_ << ")" << std::endl;
return out;
}
However, when I try to execute the code, as a result I get:
Persons attending:
P�� ()
I am not really sure what I am doing wrong. Could the problem be the scope and lifetime of variables? As I understood, a list container makes a copy of each entry, so the scope and lifetime shouldn't be the issue. I have also seen somewhere that in order for a class instance to be stored in the list, the class must have default constructor, copy constructor and an = operator overloaded. My class Person has all these characteristics.
The code I posted are just inserts that I found relevant for this issue.
If anyone could give me the slightest hint what the problem could be, it would be really appreciated.
With regards
You do have a copy-constructor, which is good since you do a lot of copying. However, you only do shallow copying, i.e. copy only the pointers and not the actual contents.
This means that if you make a copy of a Person object (like you do when calling Journey::add(Person person)) then you will have two objects both using the same pointers to the same memory. If your destructor (if you have any) free the memory, then the memory is free'd for both objects, but one of the object will still have the pointers to the now free memory, leading to undefined behavior when you try to dereference those pointers.
You need to do deep copying, in other words allocate new memory and copy the contents. Or do the sensible thing and use std::string.
I have a problem in my string class. After cin.get compiler display me this expression. Where did I go wrong?
//Source.cpp
#include <iostream>
#include <string>
#include "str.h"
using namespace std;
int main()
{
str S1 = "Hello, world!";
str S2 = "LOL";
str S3 = S2;
cout << S3.cstr() << endl;
cout << S1.size() << ": " << S1.cstr() << endl;
cin.get();
}
//str.h
#ifndef STR_H
#define STR_H
class str
{
public:
str(const char* = "");
~str();
void operator=(const char*);
void operator=(str);
const char* cstr();
int size();
private:
void newString(const char*);
char* charPtr;
int Size;
};
#endif
//str.cpp
#define _CRT_SECURE_NO_WARNINGS
#include <cstring>
using std::strlen;
using std::strcpy;
#include "str.h"
str::str(const char* cstr)
{
newString(cstr);
}
str::~str()
{
delete[] charPtr;
}
const char* str::cstr()
{
return charPtr;
}
void str::newString(const char* cstr)
{
delete[] charPtr;
Size = strlen(cstr);
charPtr = new char[Size + 1];
strcpy(charPtr, cstr);
}
void str::operator=(const char* cstr)
{
newString(cstr);
}
int str::size()
{
return Size;
}
You didn't obey the rule of three. You have user defined destructor and copy assignment operator, but you haven't defined a copy constructor. The compiler helpfully</sarcasm> defined one for you.
On this line:
str S3 = S2;
You copy initialize S3. As you can see from the rules in the linked page:
If T is a class type and the type of other is cv-unqualified version of T or a class derived from T, the constructors of T are examined and the best match is selected by overload resolution. The constructor is then called to initialize the object.
The best matching constructor happens to be the str(const str&); which was added by the compier. The default copy constructor does not make a copy of the data pointed by the charPtr but just copies the pointer instead.
When S2 goes out of scope, after cin.get();, it's destructor deletes S2.charPtr. Next, S3 is destroyed and it's destructor tries to delete S3.charPtr which has the same value as S2.charPtr and is therefore already deleted. This has undefined behaviour. Quick googling suggests that _BLOCK_TYPE_IS_VALID assertion fails if heap pointer is invalid. I'm guessing it's likely result of this undefined behaviour.
Solution: Implement the copy constructor str(const str&); so that copies don't share data.
I am working on a challenge given to me by a friend and to complete it I need to pass a mutable string into a function without prior declaration. (The function preforms some operations on the string so it must be mutable and due to constraints in the challenge I cannot declare the variable before the function call. Basically can
myFunction("abcdef");
be altered in such a way that the string is declared in the function call and passed or so that the passed string is not declared in non-mutable memory.
Here is a version which changes the call to be
myFunction("abcdef"_ncs);
I guess, this innocent addition for "non-const string" should be permissible. Here is the code:
#include <cstring>
#include <cstddef>
#include <iostream>
void myFunction(char* x) {
std::cout << "x=" << x << "\n";
}
struct tmp_string {
char* buffer;
tmp_string(char const* str, std::size_t n)
: buffer(std::strcpy(new char[n + 1], str)) {
}
tmp_string(tmp_string&& other): buffer(other.buffer) { other.buffer = 0; }
~tmp_string() { delete[] buffer; }
operator char*() { return buffer; }
};
tmp_string operator"" _ncs(char const* str, std::size_t n) {
return tmp_string(str, n);
}
int main()
{
myFunction("abcdef"_ncs);
}
I didn't use std::string primarily because there is no neat conversion from a std::string to a non-const string. The only approach I could think of would be
myFunction(&std::string("abcdef")[0]);
At least, it would also neatly clean up after itself (as does the approach using tmp_string above). Note that starting with C++11 the approach taking the address of the first byte also yields a null-terminated string (for C++03 the string wasn't guaranteed to be null-terminated; since I had trouble verifying this guarantee: it is in 21.4.5 [string.access] paragraph 2).
Here is a simple way to do this.
#include <iostream>
using namespace std;
void MyFunc(char* c)
{
c[0] = 's';
cout << c << endl;
delete[] c;
}
int main()
{
MyFunc(new char[3]{'a','b', 0});
return 0;
}
If you hand any pointer to a C++ stream, it's address will be put into the output. (Obviously unless there's a more specific output handler.)
void* px = NULL;
const char* ps = "Test";
FooType* pf = ...;
stringstream s;
s << ps << " " << px << " " << pf "\n";
s.str(); // yields, for example: "Test 0 AF120089"
This can be a problem, if the user erroneously was trying to actually print the value of FooType.
And it is also a problem when mixing wide char and narrow char, because instead of a compiler error, you'll get the address printed:
const wchar_t* str = L"Test! (Wide)";
// ...
cout << str << "\n"; // Ooops! Prints address of str.
So I was wondering - since I very rarely want to output a pointer value, would it be possible to disable formatting of pointer values, so that inserting a pointer value into a stream would result in a compiler error? (Outputting of pointer values could then be easily achieved by using a wrapper type or casting the pointer values to size_t or somesuch.)
Edit: In light of Neil's answer (disabling void* output by providing my own void* output operator) I would like to add that it would be nice if this also worked for tools such as Boost.Format, that make implicit use of the output operator defined in the std namespace ...
This gives a compilation error in g++ if the second and/or third output to cout is uncommented:
#include <iostream>
using namespace std;
ostream & operator << ( const ostream &, void * ) {
}
int main() {
int n = 0;
cout << 0;
// cout << &n;
// cout << (void *) 0;
}
A global template version of operator<< seems to work:
#include <iostream>
#include <boost/static_assert.hpp>
template<typename T>
std::ostream & operator<<(std::ostream & stream, T* value) {
BOOST_STATIC_ASSERT(false);
}
int main() {
int foo = 5;
int * bar = &foo;
std::cout << bar << std::endl;
}
Edit: This solution does not work as intended, as the template also captures string literals. You should prefer #Neil's solution.
Yes, you can cause a compilation error by providing your own overload of ostream's operator <<.
#include <iostream>
template <typename T>
std::ostream& operator << (std::ostream& s, T* p)
{
char ASSERT_FAILED_CANNOT_OUTPUT_POINTER[sizeof(p) - sizeof(p)];
}
int main()
{
int x;
int* p = &x;
cout << p;
}
Keep the operator << unimplemented for pointers.
template<typename T>
std::ostream& operator<<(std::ostream &stream, T* value);
Edit: Or better to put an invalid typename to get compiler error.