Like member functions, public member variables can include pass-by-reference capability.. Line 6 sets do_assignin to true because now we want draw to assign the object before it exits..
Trang 1environment, pass-by-reference syntax can reduce code quality and interfere with debugging If you press me, I recommend that you use pass-by-reference very sparingly Even so, if you decide
to include pass-by-reference syntax, it’s important to have a good strategy In the examples that follow, we will implement one possible strategy
Currently all functions in the shape hierarchy use standard pass-by-value syntax Consequently, all mutators must return a copy of the modified object, and a user must assign the returned object
to a variable This works well whenever it is obvious that a function is a mutator For some functions,
it is difficult to decide at a glance whether the function is a mutator In the shape hierarchy, draw
is a mutator but this fact is easy to forget Calling draw modifies mFigureHandle, but mFig-ureHandle is an internal implementation detail Clients should be shielded from internal details, and pass-by-reference is another tool that can be used to help enforce the separation between public and private
The current implementation of draw includes the following test:
if nargout ~= 1
warning(‘draw must be called using: obj = draw(obj)’);
else
Since all side effects from draw are private, a client might reasonably expect to call draw without assigning the return value In the current implementation that would result in an error The client doesn’t get the desired result, but at least the error message tells what to do With pass-by-reference emulation, draw might still perform a nargout test; however, the result of the test would no longer throw an error Instead, pass-by-reference emulation would be used to modify the input object in place The change would be immediately reflected in the client’s workspace In the examples that follow, we will modify draw by allowing it to be called as a mutator or with pass-by-reference syntax
Like member functions, public member variables can include pass-by-reference capability This allows dot-reference operations with accessor syntax to include hidden side effects These side effects require pass-by-reference assignment of the input object We will demonstrate this behavior
by modifying a class in the cShape hierarchy Rather than change the operation of an existing public variable, we will invent a new public variable called View When View is true, the object will display itself using developer view format; and when View is false, normal display format will be used To demonstrate the pass-by-reference operation, anytime View is accessed, the accessor will change the value stored in developer_view With standard pass-by-value, this change would be lost; however, with pass-by-reference, changes to the object are preserved
21.2 PASS-BY-REFERENCE FUNCTIONS
The three commands used to implement pass-by-reference emulation are inputname, assignin, and evalin These standard MATLAB functions are described as follows:
• inputname(argument_number): This function looks in the caller’s workspace and returns the name of the variable associated with the input argument at position
argument_number Thus, we can find the name of the object in the caller’s workspace
by calling inputname for this
• assignin(‘caller’, ‘name’, value): This function assigns a value to a variable in the caller’s workspace To do this, you have to know the name of the variable
in the caller’s workspace The name can be inferred from a standard naming convention,
or the name can be obtained using inputname
Trang 2Pass-by-Reference Emulation 315
• evalin(‘caller’, ‘expression’): This function evaluates an expression in
the caller’s workspace Almost any expression can be evaluated Evalin allows a
function to gather a lot of information about the caller and the caller’s workspace
The biggest limitation in pass-by-reference emulation occurs when inputname returns an
empty string This happens when the input is not a pure variable but rather the result of an operation
Among others, some common syntax examples that results in an empty inputname are x(1),
x+y, s.val, and varargin{:} Anytime our code uses inputname, we must remember to
check for an empty string and take the appropriate course of action In most cases of
pass-by-reference emulation, the correct action is an error
21.3 PASS-BY-REFERENCE DRAW
Currently draw throws an error when nargout is zero By adding inputname and assignin,
we can still assign the mutated object even when nargout is zero Additions to the beginning
and end of draw are provided in Code Listing 130 All tailored versions of draw must include
these additions
The same as before, line 3 checks the number of output arguments If nargout is one, line
4 sets the variable do_assignin to false Draw does not need to assign the object in place
because the caller is correctly using pass-by-value syntax If there is no output argument, lines
6–10 initialize the pass-by-reference variables Line 6 sets do_assignin to true because now
we want draw to assign the object before it exits Line 7 tries to get the name of the object in the
caller’s workspace If inputname returns an empty string, lines 8–10 throw an error
The comment in line 13 is a placeholder for the body of code normally included in draw We
have listed those commands before and don’t need to list them again At the end of draw, if
do_assignin is true, line 16 performs the pass-by-reference assignment The value of
callers_this is the name of the object in the caller’s workspace found on line 7
Code Listing 130, An Approximation to Call-by-Reference Behavior
1 function this = draw(this, figure_handle)
2
3 if nargout == 1
4 do_assignin = false;
6 do_assignin = true;
7 callers_this = inputname(1);
8 if isempty(callers_this)
9 error('must be called using mutator or call-by-reference
syntax')
11 end
12
13 % The guts of draw goes here
14
15 if do_assignin
16 assignin('caller', callers_this, this);
17 end
Trang 3Draw now supports mixed use of either call-by-value or call-by-reference syntax The normal call-by-value syntax doesn’t change It still looks like the following:
shape = draw(shape);
The new call-by-reference syntax omits the assignment Call by reference syntax looks like the following:
draw(shape);
Supporting both methods is not a requirement, but it is usually the right thing to do
21.4 PASS-BY-REFERENCE MEMBER VARIABLE: VIEW
If one member function can benefit from pass-by-reference behavior, maybe others can benefit too Functions outside the group of eight can use the same technique demonstrated for draw Functions included in the group of eight follow a different approach For these functions, do_assignin
isn’t assigned based on nargout, but rather it is passed from a helper function into get or set The helper function must be the controlling source because MATLAB already uses syntax to decide whether to call subsref or subsasgn
The other wrinkle in pass-by-reference emulation involves the number of function calls typically found between a client’s use of operator syntax and the helper For example, dot-reference syntax
is converted into a call to subsref, which calls get, which potentially calls a parent version of
get, which finally calls the helper If the helper needs to specify pass-by-reference operation, that request must travel all the way back into subsref The helper-function interface described in Chapter 16 gives the helper a way to kick off the process The intervening functions must now accept the arguments and make the correct assignments
As always, anchoring the example to some particular requirement makes the discussion easier
to follow As previously described, we will create a new public member named View When View
is true, the object displays using developer view format; and when View is false, the normal display format is used Like all public variables, the logical value of View may be assigned using
a dot-reference operator, by calling subsasgn, or by calling set
Unlike other public variables, we are going to add pass-by-reference behavior to View and do something that makes pass-by-reference relatively easy to observe In this example, except for the fact that it demonstrates pass-by-reference mechanics, the behavior is pointless When View is accessed, the helper will appear to use the ~ operator to reverse value of a private logical variable The helper returns this new value, the modified object, and do_assignin with a value of true Ultimately, the modified object is assigned into the client’s workspace The assignment relies on pass-by-reference code inserted into get and subsref by Class Wizard
21.4.1 H ELPERS , GET , AND SUBSREF WITH P ASS - BY -R EFERENCE B EHAVIOR
The helper initiates pass-by-reference by mutating the object and passing back a true value in
do_assignin Inside get, the do_assignin value triggers pass-by-reference commands similar to those added to draw There are a few differences because get is usually an intermediate function That is, get is usually called indirectly through subsref, not directly by the client In this situation, the proper assignment of do_assignin uses assignin This is where code organization in the group of eight proves its worth The block organization in get and subsref
makes it easier to support a general method for call-by-reference emulation The following example code demonstrates the general implementation
Trang 421.4.1.1 Pass-by-Reference Behavior in the Helper
Earlier versions of cShape did not include a public variable named View The example files in this chapter’s directory include View as an addition From previous experience, we know we can add a new public variable without introducing unexpected side effects In the Class Wizard Public Variables … dialog, add a new public variable named View and enter %helper in both the
Accessor Expression and Mutator Expression fields No further changes are required because the value of View relies on private variables that already exist Save the change and rebuild the files Class Wizard writes an initial version of View_helper.m into /@cShape/private/ The initial version must always be tailored to match the desired behavior The tailored version is shown in Code Listing 131
Code Listing 131, Enabling a Helper with Call-by-Reference Behavior
1 function [do_sub_indexing, do_assignin, this, varargout] = .
2 View_helper(which, this, index, varargin)
3
4 switch which
5 case 'get' % ACCESSOR
6 % input: index contains any additional indexing as a
substruct
7 % input: varargin empty for accessor
8 do_sub_indexing = true; % tells get.m whether to index
deeper
9 do_assignin = true; % !!! call-by-reference behavior
10 varargout = cell(1, nargout-3); % 3 known vars plus
varargout 11
12 % before the toggle [] means standard, load after-toggle
values
13 developer_sieve = cellfun('isempty',
{this.mDisplayFunc});
14 % toggle the display function, remember false means standard
15 [this(developer_sieve).mDisplayFunc] =
deal('developer_view');
16 [this(~developer_sieve).mDisplayFunc] = deal([]);
17
18 % fill varargout with the "requested" data
19 varargout = num2cell(developer_sieve);
20
21 case 'set' % MUTATOR
22 % input: index contains any additional indexing as a
substruct
23 % input: varargin contains values to be assigned into
the object
24 do_sub_indexing = false; % mutator _must_ do deep indexing
Trang 5First, let’s tackle ‘get’, the accessor On line 8, accepting the value of true allows code that already exists in get to handle any additional indices On line 9, the value returned via
do_assignin controls pass-by-reference emulation Here, the normal return value of false
has been changed to true When get receives this true value, it will trigger a series of pass-by-reference commands Next, the helper implements the desired behavior
Line 10 preallocates varargout Lines 13–19 use vector syntax to both toggle the value and fill varargout Vector syntax is always preferred because it is more efficient when this is nonscalar The value associated with the public variable View is determined by the value of the private variable mDisplayFunc Line 13 uses cellfun and ‘isempty’ to determine the logical View values returned through varargout Lines 15–16 toggle the view state by assigning
‘developer_view’ into the empty elements and empty into the others This is where the mutation occurs In an ordinary accessor, this change would never make it back to the client; however, the value returned due to change on line 9 means that this accessor is no ordinary accessor Line 19 assigns the public View values into varargout
Mutator code is conventional Lines 24–26 accept the code provided by Class Wizard In each object, View is scalar, so lines 28–30 throw an error if indexing deeper than the first dot-reference level is detected Nonscalar public variables often require a block of code to handle deeper indexing levels Line 32 converts the input cell array into a logical array, and lines 34–35 use the logical array to assign either ‘developer_view’ or empty into the proper elements
21.4.1.2 Pass-by-Reference Code in get.m
Commands in get are organized into blocks that represent public variables, concealed variables, and parent slice and forward Variables in each block are also classified as either direct-link or direct-link Direct-link variables associate one-to-one with private member variables, while non-direct-link variables use a helper function The distinction is important because pass-by-reference behavior can only be initiated by a helper Since direct-link variables don’t use a helper, they cannot initiate pass-by-reference behavior This is not a serious limitation because any direct-link variable can be easily converted into a non-direct-link variable There are no side effects, and Class Wizard automatically generates most of the non-direct-link code
25 do_assignin = false; % leave false until you read book
section 3
26 varargout = {}; % 'set' returns nothing in varargout 27
28 if ~isempty(index)
29 error('Deeper levels of indexing is not supported');
31 % true in varargin means developer view
32 developer_sieve = logical([varargin{:}]);
33 % set the display function
34 [this(developer_sieve).mDisplayFunc] =
deal('developer_view');
35 [this(~developer_sieve).mDisplayFunc] = deal([]);
36
37 otherwise
38 error('OOP:unsupportedOption', ['Unknown helper option: '
which]);
39 end
Trang 6Inside get, each non-direct-link case includes a properly configured call to a helper function Values returned by the helper function may trigger by-reference behavior The primary pass-by-reference code block can be found in chapter_0/@cShape/get.m beginning on line 175 The pass-by-reference block has been copied into Code Listing 132
The test in line 175 guards entry into block Pass-by-reference commands execute only when
do_assignin is true The first pass-by-reference command, line 176, uses the inputname
command to obtain the client’s name for the object If inputname(1) returns an empty string, pass-by-reference assignment cannot be completed and lines 178–180 issue a warning The con-ditions that lead to an empty inputname value were discussed in §21.3 If inputname(1) is not empty, lines 182–186 use the now familiar assignin command As in draw, line 182 uses
assignin to assign the modified object in the caller’s workspace Different from draw are the additional commands found in lines 183–186 These additional lines indirectly forward
do_assignin to every caller except struct.m Line 183 uses evalin to get the name of the calling module Line 184 uses strmatch to check for the string ‘struct’, and line 185 performs the indirect assignment of do_assignin
When a child class forwards get to a parent, the object is sliced and only the parent part is passed When a pass-by-reference operation is required, the parent’s get uses Code Listing 132
to assign both the mutated parent and do_assignin The child must detect a change to its own
do_assignin variable and reattach the mutated parent The parent forward block is shown in Code Listing 133; only lines 151–154 are new
Code Listing 132, Pass-by-Reference Code Block in get.m
175 if do_assignin == true
176 var_name = inputname(1);
177 if isempty(var_name)
178 warning('OOP:invalidInputname',
179 ['No assignment: pass-by-reference can only be used '
182 assignin('caller', var_name, this);
183 caller = evalin('caller', 'mfilename');
184 if isempty(strmatch(caller, {'struct'}))
185 assignin('caller', 'do_assignin', true);
188 end
Code Listing 133, Pass-by-Reference Parent Forward Assignment Commands
116 % parent forwarding block
117 if ~found
118
119 if called_by_name
120 forward_index = index(1).subs;
122 forward_index = index;
Trang 7Line 117 guards entry into the parent forward block so that line 151 is skipped if the variable has already been found If the execution reaches line 151 and do_assignin is true, it means the parent forward operation returned a mutated parent Lines 152–153 assign the parent slice back into the child The true value that remains in do_assignin allows the commands in Code Listing 132 to complete the task of indirectly assigning the mutated object into the caller’s workspace The complete process can be difficult to follow and thus difficult to debug and maintain Class Wizard takes care of the heavy lifting All you need to do is return the correct value of
do_assignin from the helper
124
125 if nargout == 0
126 varargout = cell(size(this));
128 varargout = cell(1, nargout);
130
131 for parent_name = parent_list' % loop over parent cellstr
133 parent = [this.(parent_name{1})];
134 [varargout{:}] = get(parent, forward_index,
varargin{:});
135 found = true; % catch will assign false if not found
136 do_sub_indexing = false; % assume parent did all
sub-indexing
137 found = true; % catch will assign false if not found
138 break; % can only get here if field was found
150
151 if do_assignin
152 parent = num2cell(parent);
153 [this.(parent_name{1})] = deal(parent{:});
155
156 end
Trang 821.4.1.3 Pass-by-Reference Code in subsref.m
Pass-by-reference additions in subsref follow a similar pattern The commands listed for get
in Code Listing 132 are also included in subsref These commands can be found in
chapter_21/@cShape/subsref.m on lines 63–75 These commands give subsref the ability to assign the object in the caller’s workspace These commands take care of client workspace assignments, but we aren’t quite finished with the array-reference case
We need to add some commands that will ensure that an indirect assignment into
this_subset will be correctly copied back into this To do this, we need to check the value
of do_assignin and take action when the value is true The modified array-reference case is shown in Code Listing 134
Lines 48–51 are the additional commands that support pass-by-reference emulation Like normal, line 47 forwards this_subset along with all remaining index values to subsref When the execution returns to line 48, the value of do_assignin is checked If the value of do_assignin
is true, it means that the values now stored in this_subset were indirectly assigned into
subsref’s workspace Line 50 copies the subset array back into its original indices This captures the change and allows the subsequent commands in lines 63–75 to assign the mutated object into the client’s workspace
21.4.2 O THER G ROUP - OF -E IGHT C ONSIDERATIONS
Now that get and subsref have been modified to support pass-by-reference emulation, we are
in a good position to consider the potential impact on the remaining group-of-eight functions There
is no impact on the mutators set and subsasgn because they already assign this There is also no impact on the constructor because it isn’t involved in pass-by-reference emulation That leaves display, struct, and fieldnames
Display and struct both rely on the cellstr value returned by fieldnames and on the operation of get We already know that get is involved in pass-by-reference emulation, so there might be an interaction among display, struct, fieldnames, and get We explore this interaction at the end of the test drive description
Code Listing 134, Array Reference Case in subsref.m with Pass-by-Reference Commands
41 this_subset = this(index(1).subs{:});
45 % trick subsref into returning more than 1 ans
46 varargout = cell(size(this_subset));
47 [varargout{:}] = subsref(this_subset, index(2:end));
49 % the value of this_subset has also changed
50 this(index(1).subs{:}) = this_subset;
Trang 921.5 TEST DRIVE
There aren’t as many new items in this chapter as you might have expected Pass-by-reference support commands were discussed in this chapter; however, they have been lurking in the group-of-eight functions since Chapter 18 Thus, all preexisting functions and variables have been well tested with the pass-by-reference additions New to this chapter are the View public variable and the execution of pass-by-reference commands The test drive commands in Code Listing 135 limit their scope to test only these new elements
Code Listing 135, Chapter 21 Test Drive Command Listing: Pass-by-Reference Emulation
1 >> cd '/oop_guide/chapter_21'
2 >> set(0, 'FormatSpacing', 'compact')
3 >> clear classes; fclose all; close all force;
4 >>
5 >> star = cStar;
6 >>
7 >> get(star, 'mFigureHandle')
10 >> draw(star);
11 >> get(star, 'mFigureHandle')
12 ans =
14 >>
15 >> get(star, 'mDisplayFunc')
16 ans =
18 >> star
19 star =
23 LineWeight: 'normal'
26 >> star.View
27 ans =
29 >> get(star, 'mDisplayFunc')
30 ans =
31 developer_view
32 >>
33 >> star
34 Public Member Variables
35 star.Size = [1 1 ];
36 star.ColorRgb = [0 0 1 ];
37 star.Points = [ values omitted ];
Trang 10Line 5 constructs a default cStar object, and line 7 displays the handle to star’s figure window Since star has not yet been drawn, its figure handle is empty (lines 8–9) Line 10 uses pass-by-reference syntax to draw the star A figure window opens and a blue star is drawn Now the figure handle has a value (lines 12–13) Pass-by-reference emulation code assigned the mutated object into the command window’s workspace even though line 10 contains no explicit assignment Next, we look at the pass-by-reference behavior added to View The initial value of star’s
mDisplayFunc is empty (Lines 16–17), and the expected display format is normal This can be seen in lines 19–25 Now things start to get interesting Line 26 accesses View and displays the value What isn’t so obvious is the fact that the access operation on line 26 also changed the object Lines 29–31 display the value of mDisplayFunc, and we see that it has changed With this value,
we expect developer view format from display That is exactly what we get in lines 34–53
We should also be able to assign star.View using mutator syntax Line 55 uses a dot-reference operator to assign false into View Internally, false is converted into an mDis-playFunc value of empty The assignment changes the display format back to normal Indeed,
38 star.LineWeight = 'normal';
39 star.View = [0];
40 star.Title = 'A Star is born';
41 Private Member Variables
42 star.mTitle = 'A Star is born';
43 star.cShape.mDisplayFunc = 'developer_view';
44 star.cShape.mSize = [1 1 ]';
45 star.cShape.mScale = [1 1 ]';
46 star.cShape.mPoints(1, :) = [ values omitted ];
47 star.cShape.mPoints(2, :) = [ values omitted ];
48 star.cShape.mFigureHandle = [];
49 star.cShape.mLineStyle.mDisplayFunc = [];
50 star.cShape.mLineStyle.mTempStatic = [];
51 star.cShape.mLineStyle.mColorHsv = [0.666666666666667 1 1 ]';
52 star.cShape.mLineStyle.mLineWidth = [1];
53 star.cShape.mLineStyle.mLineHandle = [];
54 >>
55 >> star.View = false;
56 >> star
57 star =
61 LineWeight: 'normal'
64 >>
65 >> get(star(1), 'View')
66 Warning: No assignment: pass-by-ref can't be used on indexed objects
67 > In cStar.get at 124
68 ans =