Simply, I don't get where the two parameters are taken from. Please forgive me if it is a silly question, but how can I find out that CwndTracer takes exactly two uint32_t parameters?
> I am a bit confused about the use of callbacks. > in particular, in the example named 'tcp-large-transfer.cc', there's a > function called 'CwndTracer':
> Simply, I don't get where the two parameters are taken from. > Please forgive me if it is a silly question, but how can I find out > that CwndTracer takes exactly two uint32_t parameters?
It's not a silly question at all. The implementation is non-trivial. Fortunately, you only have to figure this out once. It may be easier to just accept that when you trace a plain-old data type, you need to define a function that receives the old and the new value. i.e., don't ask :-)
Here are the internals. Look at the connect to begin putting the pieces together.
Then take a look at the path string, reading backward. The last part of the path is the attribute defining the trace source. Probably the easiest thing to do to get your bearings is just recursively grep for this Attribute name.
It's the AddTraceSource call that you are interested in. If you are using the standard ns-3 TCP, you would look in the file internet-stack/tcp-socket-impl.cc to go further. You need to identify the trace source. Look in this file for the .AddTraceSource you see in the grep results. You will see,
.AddTraceSource ("CongestionWindow", "The TCP connection's congestion window", MakeTraceSourceAccessor (&TcpSocketImpl::m_cWnd))
Look at the MakeTraceSourceAccessor call and find that the actual traced variable is
TcpSocketImpl::m_cWnd
Now, go and look in tcp-socket-impl.h for the declaration of this variable. This is a
TracedValue<uint32_t>
Unless you know where this is defined you are probably stuck here. Again, grep is your friend. Nothing interesting will show up in '*.cc' so try '*.h'
If you do this you will find lots and lots of hits in ./core/traced-value.h which is the next place you need to look.
You will find that the way these things work is to overload the operators that cause the value to be changed. So you look for the = operator. If you look there,
You will find a call to Set(). There you will find:
void Set (const T &v) { if (m_v != v) { m_cb (m_v, v); m_v = v; } }
One thing to point out is that the trace hook will only be called if the new value is different than the old value. This sometimes surprises people.
Anyway, the variable v is the new value and m_v is the existing (old) value, Notice the line,
m_cb (m_v, v);
This is the callback that is hit when you make the change to the variable by doing m_variable = something.
In the TracedValue class you can find,
private: T m_v; TracedCallback<T,T> m_cb;
So, in the case of TracedValue<uint32_t>, the T becomes int32_t and you have
int32_t v int32_t m_v
So m_cb is calling (via an overloaded operator () ) a method that takes two uint32_t, the first parameter is the existing (old) value and the second is the new value.
So,
MakeCallback (&CwndTracer)
Needs to hook a function that takes two uint32_t, the first being the old value and the second being the new value; which it does:
first of all, thank you very much for the thorough and insightful
answer!
I so appreciated it.
> [snip]
> TcpSocketImpl::m_cWnd
> Now, go and look in tcp-socket-impl.h for the declaration of this variable.
I was able to get there. Anyway, before reading your explanation I
couldn't get from there on.
> [...] in ./core/traced-value.h [...]
> You will find that the way these things work is to overload the operators
> that cause the value to be changed. So you look for the = operator. If you
> look there [...] you will find a call to Set()
It didn't occur to me that changes happen only when the variable *is
actually changed*.
And the variable is changed by means of =, ++, +=, and so on.
In fact, all those operators contain a call to the Set method.
To be honest, I still don't get the motivations behind the macro
(TRACED_VALUE_DEBUG), but that's a minor issue anyway.
> One thing to point out is that the trace hook will only be called if the new
> value is different than the old value. This sometimes surprises people.
Is it because tracking a variable on a regular time basis is more
difficult in discrete event simulators (and doesn't add anything)?
> So, in the case of TracedValue<uint32_t>, the T becomes int32_t and you have
thanks to the conversion operator:
operator T () const { return m_v; }
> int32_t v
> int32_t m_v
> So m_cb is calling (via an overloaded operator () ) a method that takes two
> uint32_t, the first parameter is the existing (old) value and the second is
> the new value.
> Non trivial, but now you know how all TracedValue work since it is
> templated.
Definitely non trivial, at least for my programming skills.
Again thanks for taking the time to write such a clear explanation.
when trying to connect my TracedValue<bool> to a function with func
(std::string, bool, bool). If the function is just (std::string, bool)
it works (and seems to be getting only the new values). I'm setting
the TracedValue with () though, not with =.
On Sep 12, 1:04 am, Antti Mäkelä <zar...@gmail.com> wrote:
> What if I connect WITH context? Context is passed as a string,
> right?
> I'm getting error as
Whoops - turns out that I followed examples related to v4ping, which
use TracedCallback<Type> instead of TracedValue.
I guess TracedCallback is somewhat of a generic version for passing
multiple parameters, and TracedValue for singular ones that you can do
slight arithmetic with?
> when trying to connect my TracedValue<bool> to a function with func
> (std::string, bool, bool). If the function is just (std::string, bool)
> it works (and seems to be getting only the new values). I'm setting
> the TracedValue with () though, not with =.
The error you posted tells that you defined a function that accepts
three arguments (one string and two booleans) but he expects to find a
function taking just two arguments (one string and one boolean).
As a matter of fact, you admit that it works in the case you define a
callback that accepts the two parameters string and bool.
Out of my curiosity, what's the variable you're trying to trace?
As far as I understand, if the variable is based on the template
defined in src/core/traced-value.h you should be able to trace it
thanks to the callback, which is of the form:
TracedCallback<T,T> m_cb;
and hence it only accepts two parameters of the same type (then,
ConnectWithContext adds a string, but that's another story); still,
you seem to be using something different.
> I guess TracedCallback is somewhat of a generic version for passing > multiple parameters, and TracedValue for singular ones that you can do > slight arithmetic with?
There is a more rigorous definition. The documentation of TracedValue talks about value semantics and so implies that a TracedValue is a trace source that captures changes to an object with value semantics.
In general, value semantics just means that you can pass the object around, not an address. In order to use value semantics at all you have to have an object with an associated copy constructor and assignment operator available. We extend the requirements to talk about the set of operators that are pre-defined for plain-old-data (POD) types. Operator=, operator++, operator--, operator+, operator==, etc., etc. You can see the list in src/core/traced-value.h if you want.
I don't think there any instance in our codebase of a TracedValue tracing anything but POD (uin32_t, int8_t, etc.), so I tend to think about TracedValue in terms of tracing POD -- ints, chars, longs, shorts, etc. You can extend this to user defined types, but like I said I don't think anyone has done that yet. The signature of the trace sink is fixed since the semantics are fixed (you get the old *value* and the new *value* of the object with *value* semantics).
A TracedCallback is a completely different animal. There is no object with value semantics in sight. What you are doing with a traced callback is just executing a callback. TracedCallback is essentially just a point-to-multipoint connection between the trace source and sinks. You can define a callback any way you want, so the parameter list can be whatever you want. The compiler just makes sure that things match.
TracedValue -> objects with value semantics TracedCallback -> generic callback
On Sep 12, 2:11 am, <crai...@ee.washington.edu> wrote:
> I don't think there any instance in our codebase of a TracedValue tracing
> anything but POD (uin32_t, int8_t, etc.), so I tend to think about
> TracedValue in terms of tracing POD -- ints, chars, longs, shorts, etc. You
Ok, currently I'm using tracecallbacks of type "Bool" and "Time". I
guess bool could work as TracedValue as well, but how about Time?
V4ping application uses TracedCallback<Time> m_traceRtt;
Actually, I implemented my own "has the state changed" check in the
callback since I didn't truly understand the difference - all I had to
go on with were the examples. So thank you very much for this info.
Maybe this thread could be edited a bit and copypasted to the
documentation? I mean, currently http://www.nsnam.org/docs/manual.html has "2.3.3, Tracing Subsystem" and NOTHING else.
> Ok, currently I'm using tracecallbacks of type "Bool" and "Time". I > guess bool could work as TracedValue as well, but how about Time? > V4ping application uses TracedCallback<Time> m_traceRtt;
This kind of thing is actually pretty easy to check out (okay, okay, if you know what to type). Copy the following into, say, scratch/test.cc and build it. I think you'll find it works fine. TracedValue<Time> will work if you stay away from the operators that aren't defined for a Time (e.g., ++, --).
I'm still a bit confused. This time about the use of
'MakeBoundCallback'
What I would like to achieve is to have all the ascii traces dumped
into a unique file.
So, by looking at how EnableAscii is implemented, I thought what I
need was 'MakeBoundCallback'. However it is not clear to me how to
build the callback (in particular, different files show different
implementations, e.g. CsmaHelper::AsciiRxEvent in src/helper/csma-
helper.cc and TxCallback in stats/wifi-example-sim.cc).
> > I am a bit confused about the use of callbacks. > I'm still a bit confused. This time about the use of > 'MakeBoundCallback'
I'm waiting for a bunch of tests to finish so I have some time to go into this now, so you get a bunch of words for free :-)
Allow me to go back and develop all of this callback stuff from first principles. Warning: I haven't tried all of this code, and a lot of it's just from memory so take it with a grain of salt.
Ultimately, the goal of a callback is just to have one piece of code call a function or method in another piece of code. Callbacks are used when you need to call different functions or methods but don't want to change the code that is doing the calling.
This means you need some kind of indirection -- you treat the address of the called function as a variable. This variable is a pointer-to-function variable. The relationship between function and function pointer is really no different that that of object and object pointer.
In C the canonical example of a function pointer is a pointer-to-function-returning-integer (PFI). For a PFI taking one int parameter, this could be declared like,
int (*pfi)(int arg) = 0;
What you get from this is a variable named simply "pfi" that is initialized to the value 0. If you want to initialize this pointer, you have to have a function with a matching signature. In this case,
int MyFunction (int arg) {}
If you have this target, you can initialize the variable to point to your function like,
pfi = MyFunction;
You can then call MyFunction indirectly using the more suggestive form of the call,
int result = (*pfi) (1234);
This is suggestive since it looks like you are dereferencing the function pointer just like you would dereference any pointer. Typically, however, people take advantage of the fact that the compiler is smart and just use a shorter form,
int result = pfi (1234);
Notice that the function pointer obeys value semantics, so you can pass it around. Typically, when you use an asynchronous interface you will pass some entity like this to the function so it can execute it and "call back" to let you know it completed.
In C++ you have the added complexity of pointer to member function, but it looks only slightly different (PMI -- pointer-to-member-function-returning-int instead of PFI).
int (MyClass::*pmi) (int arg) = 0;
This declares a variable named "pmi." Your goal will be to call a method of an instance of that particular class, so you have to declare that method in a class.
class MyClass { public: int MyMethod (int arg); };
You would then initialize your variable to point to that method like this,
pmi = &MyClass::MyMethod;
This assigns the address of the method to the variable, completing the indirection. In order to call this method, you need to have a "this" pointer which means there must be an object of MyClass around.
int (MyClass::*pmi) (int arg) = 0; // Declare a variable that is a method pointer pmi = &MyClass::MyMethod; // Point the variable at the code for the method
MyClass myClass; // Instantiate an instance of the class (myClass.*pmi) (1234); // Call the method indirectly providing the "this" pointer
Just like in the C example, you can use this in an asynchronous call to be "called back." The straightforward extension you might consider is to pass a pointer to your object and the pmi variable to the function that will call you back.
You might ask yourself at this time, what's the point? You will be permanently wiring your callback to the specific type of the called class. Why not just accept this and pass an object pointer (do object->Method (1234) in the calling code). Well, we want to avoid that! What is needed is to decouple the calling function from the called class completely. This requirement led to the development of Functors.
A functor is the outgrowth of something invented in the 1960s called a closure. It's basically just a packaged-up function call, possibly with some state (which is really the answer to your question, but I'll get to that shortly). There is really a state part and a decoupled-callback part to the puzzle. I'll talk about decoupling the callbacks first.
A functor has two parts, a specific part and a generic part. This is an inheritance relationship. The calling code (that executes the callback) will execute the generic overloaded operator () of a generic functor to cause the callback to be called. The called code will have to provide a specialized implementation of the virtual operator () that does the class-specific stuff that caused the close-coupling problem above. The called code (that made the callback) then gives the specialized code to the calling module (that will execute the callback). The calling module takes a generic functor as a parameter so a cast is done in the function call. The calling module just needs to understand the generic functor type.
The information you'll need to make a specific functor is the "this" pointer and the pointer-to-method. So, you might have a function called MakeFunctor (&MyClass::MyMethod, this) that put together a specific functor. If this sounds like the ns-3 MakeCallback, then you are understanding.
There is a lot of template black magic going on in ns-3 to deal with various issues, especially variable formal parameter lists, but the essence is that the system declares a generic part of the functor:
And the caller cooks up a specific part of the functor that really is just there to implement the specific operator() method.
template <typname T, typename ARG> class SpecificFunctor : public Functor { void (T::*m_cb)(ARG arg); T* m_p; public: SpecificFunctor(T* p, int (T::*_cb)(ARG arg)) { m_p = p; m_cb = cb; }
virtual int operator() (ARG arg) { (*m_p.*m_cb)(arg); } };
Notice that when operator() is called, it in turn calls the callback function with the object pointer using the C++ PMI syntax with the provided argument.
You could then declare some code that takes a generic functor,
void LibraryFunction (Functor functor);
And build a specific functor in the client and pass this to the LibraryFunction,
Notice that the LibraryFunction is completely decoupled from the specific implementation of the client. The connection is made through the Functor polymorphism.
Soooo, if you just replace the term Functor above with Callback, and instead of declaring the SpecificFunctor directly, create it dynamically through something called MakeCallback you have reconstructed the ns-3 callback system.
MakeCallback (&Function);
Will automagically do the C-style callback / functor for you, and
Will automagically do the C++-style callback / functor for you.
The last part of this puzzle is the MakeBoundCallback part. At the beginning, I mentioned closures and the fact that they were function calls packaged up for later. Notice that in the Functor definitions above, there is no way to package up any parameters for use later when the functor is called via operator(). All of the parameters are provided by the LibraryFunction.
What if you want to allow the client function to provide some of the parameters? Andrei Alexandrescu calls the process of allowing a client to specify one of the parameters "binding." This is what the "Bound" refers to in MakeBoundCallback. One of the parameters of operator() has been bound (fixed) by the client.
CsmaHelper::SniffEvent() is a great example of this. If you can sort through this one, you understand it all! Look at src/helper/csma-helper.h and you will find,
This means that CsmaHelper::SniffEvent() is a function, not a method. There is no this pointer, so you will be using C-style callbacks. If you look at the definition of CsmaHelper::SniffEvent you will find a function that takes two parameters.
The callback function takes two parameters. Now look in src/devices/csma-net-device.cc for the other half of this arrangement. The Config::Connect is going to connect a callback to some instance of a member Attribute that is given Callback powers. Recalling the explanation of how to find what is happening in trace sources, look for the Attribute "PromiscSniffer" and you will find,
.AddTraceSource ("PromiscSniffer", "Trace source simulating a promiscuous packet sniffer attached to the device", MakeTraceSourceAccessor (&CsmaNetDevice::m_promiscSnifferTrace))
You know from previous discussions that you need to now look for m_promiscSnifferTrace. If you locate an instance of this you will see,
m_promiscSnifferTrace (m_currentPkt);
When the callback is executed, the calling code only provides one of the parameters. Where does the other one required by CsmaHelper::SniffEvent come from? Look at MakeBoundCallback back in the CsmaHelper:
> I'm waiting for a bunch of tests to finish so I have some time to go into
> this now, so you get a bunch of words for free :-)
The greatest gift ever :)
> Hope this is all starting to make sense.
Actually, yes. The mechanisms behind all this are clearer now.
> > What I would like to achieve is to have all the ascii traces dumped
> > into a unique file.
> > So, by looking at how EnableAscii is implemented, I thought what I
> > need was 'MakeBoundCallback'. However it is not clear to me how to
> > build the callback (in particular, different files show different
> > implementations, e.g. CsmaHelper::AsciiRxEvent in src/helper/csma-
> > helper.cc and TxCallback in stats/wifi-example-sim.cc).
> Can you see a way to get this done now?
If I got it correctly, I should do something like:
int
main (int argc, char *argv[])
{
// 2) making such a bound callback
std::ofstream ascii;
ascii.open ("adhoc-transfer.tr");
Config::ConnectWithoutContext ("/NodeList/0/$ns3::TcpL4Protocol/
SocketList/0/CongestionWindow", MakeBoundCallback (&CwndChangeAscii,
ascii));
}
Still, it doesn't compile. There are some errors regarding the type
passed.
And there are also errors regarding the callback:
/usr/include/c++/4.3/bits/ios_base.h: In copy constructor
‘std::basic_ios<char, std::char_traits<char> >::basic_ios(const
std::basic_ios<char, std::char_traits<char> >&)’:
/usr/include/c++/4.3/bits/ios_base.h:782: error:
‘std::ios_base::ios_base(const std::ios_base&)’ is private
/usr/include/c++/4.3/iosfwd:52: error: within this context
/usr/include/c++/4.3/iosfwd: In copy constructor
‘std::basic_ofstream<char, std::char_traits<char> >::basic_ofstream
(const std::basic_ofstream<char, std::char_traits<char> >&)’:
/usr/include/c++/4.3/iosfwd:89: note: synthesized method
‘std::basic_ios<char, std::char_traits<char> >::basic_ios(const
std::basic_ios<char, std::char_traits<char> >&)’ first required here
/usr/include/c++/4.3/streambuf: In copy constructor
‘std::basic_filebuf<char, std::char_traits<char> >::basic_filebuf
(const std::basic_filebuf<char, std::char_traits<char> >&)’:
/usr/include/c++/4.3/streambuf:775: error:
‘std::basic_streambuf<_CharT, _Traits>::basic_streambuf(const
std::basic_streambuf<_CharT, _Traits>&) [with _CharT = char, _Traits =
std::char_traits<char>]’ is private
/usr/include/c++/4.3/iosfwd:83: error: within this context
/usr/include/c++/4.3/iosfwd: In copy constructor
‘std::basic_ofstream<char, std::char_traits<char> >::basic_ofstream
(const std::basic_ofstream<char, std::char_traits<char> >&)’:
/usr/include/c++/4.3/iosfwd:89: note: synthesized method
‘std::basic_filebuf<char, std::char_traits<char> >::basic_filebuf
(const std::basic_filebuf<char, std::char_traits<char> >&)’ first
required here
../scratch/adhoc_multihop_app.cc: In function ‘int main(int, char**)’:
../scratch/adhoc_multihop_app.cc:239: note: synthesized method
‘std::basic_ofstream<char, std::char_traits<char> >::basic_ofstream
(const std::basic_ofstream<char, std::char_traits<char> >&)’ first
required here
../scratch/adhoc_multihop_app.cc:239: error: initializing argument 2
of ‘ns3::Callback<R, T1, T2, ns3::empty, ns3::empty, ns3::empty,
ns3::empty, ns3::empty, ns3::empty, ns3::empty> ns3::MakeBoundCallback
(R (*)(TX, T1, T2), ARG) [with R = void, TX = uint32_t, ARG =
std::basic_ofstream<char, std::char_traits<char> >, T1 = uint32_t, T2
= std::ofstream&]’
debug/ns3/callback.h: In constructor ‘ns3::BoundFunctorCallbackImpl<T,
R, TX, T1, T2, T3, T4, T5, T6, T7, T8>::BoundFunctorCallbackImpl
(FUNCTOR, ARG) [with FUNCTOR = void (*)(unsigned int, unsigned int,
std::ofstream&), ARG = std::basic_ofstream<char,
std::char_traits<char> >, T = void (*)(unsigned int, unsigned int,
std::ofstream&), R = void, TX = unsigned int, T1 = unsigned int, T2 =
std::ofstream&, T3 = ns3::empty, T4 = ns3::empty, T5 = ns3::empty, T6
= ns3::empty, T7 = ns3::empty, T8 = ns3::empty]’:
debug/ns3/ptr.h:243: instantiated from ‘ns3::Ptr<T> ns3::Create(T1,
T2) [with T = ns3::BoundFunctorCallbackImpl<void (*)(unsigned int,
unsigned int, std::ofstream&), void, unsigned int, unsigned int,
std::ofstream&, ns3::empty, ns3::empty, ns3::empty, ns3::empty,
ns3::empty, ns3::empty>, T1 = void (*)(unsigned int, unsigned int,
std::ofstream&), T2 = std::basic_ofstream<char, std::char_traits<char>
>]’
debug/ns3/callback.h:925: instantiated from ‘ns3::Callback<R, T1,
T2, ns3::empty, ns3::empty, ns3::empty, ns3::empty, ns3::empty,
ns3::empty, ns3::empty> ns3::MakeBoundCallback(R (*)(TX, T1, T2), ARG)
[with R = void, TX = uint32_t, ARG = std::basic_ofstream<char,
std::char_traits<char> >, T1 = uint32_t, T2 = std::ofstream&]’
../scratch/adhoc_multihop_app.cc:239: instantiated from here
debug/ns3/callback.h:287: error: invalid conversion from ‘void*’ to
‘unsigned int’
The same happens if I pass, say, a class instance.
If I pass, say, a char or an int, the code compiles but it doesn't
trace cwnd changes (not even using wildcards in lieu of node numbers)
although packets are exchanged, according to the .pcap trace.
Heh, heh. You have run into the really hard part -- the inevitable error messages.
You are closer than you may think, though.
If it were me, I would try something new on a scale much smaller than you have attempted. I would take the "simplest example" that I posted a few days ago as a starting point and make a tiny change to try and turn it into a bound callback to see what happened. If you did that, you might end up with:
You will find that this example works fine (Notice your version got the bound value in the wrong place).
A physics professor of mine once called the following technique adiabatic programming. Make a single tiny change to a working program. The next tiny change would be to try and use the stream as the bound parameter.
But then when you try and build it -- boom, it blows up in your face.
---------- Begin Included Capture ----------
[ns-regression] ~/repos/ns-3-allinone-test/ns-3-dev > ./waf Waf: Entering directory `/home/craigdo/repos/ns-3-allinone-test/ns-3-dev/build' [639/740] cxx: scratch/test-bound.cc -> build/debug/scratch/test-bound_2.o /usr/include/c++/4.2/bits/ios_base.h: In copy constructor â?~std::basic_ios<char, std::char_traits<char> >::basic_ios(const std: :basic_ios<char, std::char_traits<char> >&)â?T: /usr/include/c++/4.2/bits/ios_base.h:779: error: â?~std::ios_base::ios_base(const std::ios_base&)â?T is private /usr/include/c++/4.2/iosfwd:55: error: within this context /usr/include/c++/4.2/iosfwd: In copy constructor â?~std::basic_ofstream<char, std::char_traits<char> >::basic_ofstream(const std ::basic_ofstream<char, std::char_traits<char> >&)â?T: /usr/include/c++/4.2/iosfwd:92: note: synthesized method â?~std::basic_ios<char, std::char_traits<char> >::basic_ios(const std:: basic_ios<char, std::char_traits<char> >&)â?T first required here /usr/include/c++/4.2/streambuf: In copy constructor â?~std::basic_filebuf<char, std::char_traits<char> >::basic_filebuf(const st d::basic_filebuf<char, std::char_traits<char> >&)â?T: /usr/include/c++/4.2/streambuf:794: error: â?~std::basic_streambuf<_CharT, _Traits>::basic_streambuf(const std::basic_streambuf< _CharT, _Traits>&) [with _CharT = char, _Traits = std::char_traits<char>]â?T is private /usr/include/c++/4.2/iosfwd:86: error: within this context /usr/include/c++/4.2/iosfwd: In copy constructor â?~std::basic_ofstream<char, std::char_traits<char> >::basic_ofstream(const std ::basic_ofstream<char, std::char_traits<char> >&)â?T: /usr/include/c++/4.2/iosfwd:92: note: synthesized method â?~std::basic_filebuf<char, std::char_traits<char> >::basic_filebuf(con st std::basic_filebuf<char, std::char_traits<char> >&)â?T first required here ../scratch/test-bound.cc: In function â?~int main(int, char**)â?T: ../scratch/test-bound.cc:45: note: synthesized method â?~std::basic_ofstream<char, std::char_traits<char> >::basic_ofstream(cons t std::basic_ofstream<char, std::char_traits<char> >&)â?T first required here ../scratch/test-bound.cc:45: error: initializing argument 2 of â?~ns3::Callback<R, T1, T2, ns3::empty, ns3::empty, ns3::empty, ns3::empty, ns3::empty, ns3::empty, ns3::empty> ns3::MakeBoundCallback(R (*)(TX, T1, T2), ARG) [with R = void, TX = std::ofstre am, ARG = std::basic_ofstream<char, std::char_traits<char> >, T1 = uint32_t, T2 = uint32_t]â?T Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone-test/ns-3-dev/build' Build failed -> task failed (err #1): {task: cxx test-bound.cc -> test-bound_2.o} [ns-regression] ~/repos/ns-3-allinone-test/ns-3-dev >
---------- End Included Capture ----------
But all you have done is changed the type of the bound variable!
This must be due to that variable change. It turns out that an ofstream doesn't have a copy constructor. Think about what it would mean to copy and ofstream. What are the semantics? Since you can't predict that correctly, ofstream doesn't allow copies.
It turns out that an ofstream doesn't have value semantics!
You have run into a problem using the stl, not ns-3 :-)
You have to pass around the *address* of the ofstream instead of passing it by value.
We usually frown on passing pointers around in ns-3, but prefer to use Objects and our reference counting system. The example above works, but you have got to be very, very careful about the lifetime of that ofstream.
If you look at the CsmaHelper, you will see that the MakeBoundCallback takes a Ptr<PcapWriter> as the bound parameter. This points to an Object that, in turn, holds the ofstream object; and so you can pass the Ptr<PcapWriter> around by value just fine. As long as someone has a reference to the underlying writer it stays around taking care of the lifetime issue automagically.
So I would take that approach and make my own kind of writer.
I think you are getting really, really close here.
Thank you so much!
It works, plus now I got where I was stuck.
> We usually frown on passing pointers around in ns-3, but prefer to use
> Objects and our reference counting system. The example above works, but you
> have got to be very, very careful about the lifetime of that ofstream.
> If you look at the CsmaHelper, you will see that the MakeBoundCallback takes
> a Ptr<PcapWriter> as the bound parameter. This points to an Object that, in
> turn, holds the ofstream object; and so you can pass the Ptr<PcapWriter>
> around by value just fine. As long as someone has a reference to the
> underlying writer it stays around taking care of the lifetime issue
> automagically.
> So I would take that approach and make my own kind of writer.
I've noticed that every EnableAscii() resorts to AsciiWriter, but I
thought it was overly complicated for my programming skills (that's
why I decided to use just an ostream, which turns out to be more harm
than good as the programmer in question doesn't know how to tame wild
pointers :).
I tried writing my own AsciiWriter and have some scope issues. But
since this problem is not Callback-related, I'd better start another
topic.